JavaScript voor beginners: Frames & Navigatie

In de vorige aflevering hebben we gezien hoe we in JavaScript gebruik maken van "event handlers" om te reageren op gebeurtenissen als het indrukken van de knop in een formulier. In deze aflevering zullen we zien hoe we in JavaScript omgaan met frames, en hoe we van de ene pagina naar de andere kunnen navigeren.

Frames

Het kan handig zijn om een webpagina op te delen in een aantal "frames", bijvoorbeeld een menu aan de linkerkant van het scherm en de "hoofdpagina" rechts daarvan. Als de bezoeker van de site op een link in het menu klikt, wordt een nieuwe hoofdpagina getoond, terwijl het menu gewoon blijft staan. Er zijn ook sites die naast het menu en de hoofdpagina nog een extra frame boven aan de pagina bevatten waarin hetzij het logo van de site, hetzij een banneradvertentie wordt getoond.

Frames bieden je dus de mogelijkheid om een scherm op te delen in twee of meer stukken waarvan sommige (zoals het menu of de reclameboodschap) voortdurend in beeld blijven, ook als de bezoeker van de site de tekst in een ander frame omhoog of omlaag scrollt.

Frames hebben nog enkele voordelen: ze stellen je bijvoorbeeld in staat om de lay-out van je pagina preciezer te specificeren, en ze maken het mogelijk om pagina's van een andere website te tonen als een geïntegreerd onderdeel van je eigen site; op dergelijke aspecten zullen we hier niet nader ingaan.

Dit klinkt heel handig, en een tijd lang waren websites met frames erg populair. Helaas hebben frames ook een aantal belangrijke nadelen, en als gevolg daarvan maken de meeste (professionele) websites tegenwoordig geen gebruik meer van frames.

Het is bijvoorbeeld lastig om een bepaald frame te printen of op te nemen in je bookmarks; daarnaast zijn veel zoekmachines niet in staat om websites met frames correct te indexeren. Ook het gebruik van JavaScript is op dergelijke sites meestal ingewikkelder dan op frameloze sites.

We kunnen bijvoorbeeld vanuit het ene frame verwijzen naar een object in het andere. Aangezien de inhoud van een frame soms kan wijzigen, moeten we in zo'n geval goed opletten dat het object waarnaar we verwijzen op dat moment ook inderdaad bestaat!

HTML

Om een pagina met frames te maken, gebruiken we twee speciale HTML-tags: FRAMESET en FRAME. We schrijven bijvoorbeeld:

<HTML>
<FRAMESET FRAMEBORDER=NO BORDER=0 COLS="300, *">
<FRAME NAME="a" SRC="a.htm">
<FRAME NAME="b" SRC="b.htm">
</FRAMESET>
</HTML>


Door middel van het COLS-attribuut van de FRAMESET-tag hebben we de pagina horizontaal in tweeën gedeeld: een linkerdeel dat 300 pixels breed is, en een rechterdeel dat de rest ("*") van het venster in beslag neemt. Het eerste (linker) frame heet "a" en krijgt zijn inhoud vanuit het bestand "a.htm"; in het rechter frame "b" wordt de inhoud van het bestand "b.htm" getoond. De twee frames hoeven geenszins naar elkaar te verwijzen: het zijn in principe twee onafhankelijke vensters.

Frames zijn namelijk "instances" of concrete voorbeelden van het Window-object (er bestaat strikt genomen geen Frame-object), en ze hebben dan ook de eigenschappen, methoden en gebeurtenissen van dat object. Net zoals een "gewoon" venster (het browser-venster van het hoogste niveau, oftewel het enige venster bij een site die geen gebruik maakt van frames!) frames kan bevatten, kan een frame zelf bijvoorbeeld ook weer een aantal sub-frames bevatten; op die manier kan er een hele hiërarchie aan frames ontstaan.

Om naar een specifiek frame te kunnen verwijzen, maken we gebruik van de eigenschappen "parent" en "top". Bij het browser-venster van het hoogste niveau verwijzen die twee eigenschappen altijd naar het venster zelf. Bij een frame verwijst "top" eveneens naar het browser-venster van het hoogste niveau, terwijl "parent" verwijst naar het venster dat een niveau hoger staat in de hiërarchie. De "parent" van de frames a en b uit ons voorbeeld is dus eveneens het browser-venster van het hoogste niveau, maar als frame a zelf de frames c en d zou bevatten, dan zou a de parent van c en d zijn.

Laten we aannemen dat frame b een formulier "klant" bevat met een veld "achternaam". Hoe kunnen we nu vanuit frame a verwijzen naar dat veld? We kunnen het niet hebben over"b.klant.achternaam", want binnen frame a is b helemaal niet bekend! In plaats daarvan maken we gebruik van het feit dat de parent van a ook de parent van b is, en we schrijven: "parent.b.klant.achternaam". We verwijzen dus eerst met "parent" naar het browser-venster van het hoogste niveau. Binnen dat venster is frame b wel bekend (b is een eigenschap van dat venster), en vervolgens kunnen we op de "gebruikelijke" manier verwijzen naar achtereenvolgens het formulier binnen dat frame en het veld binnen het formulier.

Indien we vanuit frame c naar ditzelfde veld zouden willen verwijzen, zouden we dit kunnen doen door middel van: "parent.parent.b.klant.achternaam". We gaan dan eerst via de parent (dat is het browser-venster) van de eigen parent (dat is frame a) naar het hoogste niveau; aldaar is vervolgens frame b weer bekend. We kunnen ook met "top" direct naar het hoogste niveau, en we schrijven dan: "top.b.klant.achternaam".

Zoals de voorbeelden wel laten zien, wordt het al gauw ingewikkeld om vanuit het ene frame naar het andere te verwijzen. Dat geldt in nog sterkere mate als de inhoud van een frame kan "wisselen": indien frame a bijvoorbeeld het ene moment wel sub-frames c en d bevat, maar het andere moment niet.

Dat kan het geval zijn als het bestand waarmee frame a initieel is "gevuld" --"a.htm" in ons voorbeeld-- een frameset met frames c en d definieert, terwijl later in frame a een nieuw bestand wordt geladen dat geen frameset definieert maar een "gewone" pagina bevat.

In zo'n geval moeten we de relatieve "positie" van het ene frame ten opzichte van het andere heel precies bepalen voordat we vanuit dat frame naar het andere kunnen verwijzen, anders krijgen we een foutmelding.

We hebben tot nu toe naar frames verwezen door middel van hun naam; we kunnen echter ook gebruik maken van het frames-array. Deze venster-eigenschap verwijst naar de verschillende frames die het venster (direct) bevat; de telling begint, zoals altijd bij arrays, bij nul.

Om te weten hoeveel frames het venster bevat, kijken we naar "frames.length". In ons voorbeeld geldt: "top.frames.length" is 2, want het browser-venster bevat de frames a en b. Indien frame a zelf de frames c en d bevat, dan is "top.frames[0].frames.length" eveneens 2. Immers, frames[0] verwijst naar frame a, en dit frame is zelf ook weer een venster met een eigenschap "frames".

Eerder zagen we al dat we de relatieve positie van frames ten opzichte van elkaar goed in de gaten moesten houden. Iets soortgelijks geldt voor onze "timing": op het moment dat we bijvoorbeeld een functie in een ander frame aanroepen, moeten we er zeker van zijn dat die functie ook inderdaad "aanwezig" is.

Als we een functie aanroepen voordat het document met die functie in het aangeroepen frame is ingeladen, krijgen we een foutmelding. Dit probleem kunnen we vaak oplossen door een permanent hulpframe te gebruiken.

Hulpframes

In sommige gevallen kan het handig zijn om gebruik te maken van een onzichtbaar hulpframe dat permanent aanwezig is en bijvoorbeeld een aantal variabelen en functies bevat. Op deze manier kunnen we de waarde van die variabelen eenvoudig opvragen vanuit diverse frames. We definiëren bijvoorbeeld in HTML drie frames:

<HTML>
<FRAMESET FRAMEBORDER=NO BORDER=0 COLS="0, 300, *">
<FRAME NAME="hulp" SRC="hulp.htm">
<FRAME NAME="a" SRC="a.htm">
<FRAME NAME="b" SRC="b.htm">
</FRAMESET>
</HTML>


Het frame "hulp" is nul pixels breed, en dus onzichtbaar. (Merk op dat we door de toevoeging van een extra frame nu naar frame a zouden moeten verwijzen door middel van "top.frames[1]" in plaats van "top.frames[0]". In het algemeen is het daarom handiger om gebruik te maken van de naam van een frame.)

Het hulpframe kan JavaScript bevatten waarmee we een variabele z definiëren waaraan we de waarde 1 toekennen ("var z=1"). Als we vervolgens in frame a of b schrijven: "alert(top.hulp.z)", dan wordt er een alertbox met "1" getoond. Op vergelijkbare wijze kunnen we vanuit frame a de functie test() aanroepen die we in het hulpframe hebben gedefinieerd, met behulp van "top.hulp.test()".

Het hulpframe bevat dan bijvoorbeeld het volgende:

<SCRIPT>
var z=1
function test()
{
alert(z)
}
</SCRIPT>


en zowel frame a als frame b bevat een button met de volgende event handler:

onClick="top.hulp.z++;top.hulp.test()"

Als we nu op een van de buttons drukken, dan wordt eerst variabele z in het hulpframe met een opgehoogd, en vervolgens wordt door middel van functie test() de waarde getoond die z op dat moment heeft. Op het scherm verschijnen dus alertboxen met achtereenvolgens de waarde "2", "3", "4", enzovoort.

Je zou kunnen denken dat we hetzelfde resultaat kunnen bereiken door zowel in frame a als in frame b een variabele z te definiëren en die variabele steeds op te hogen. Dat is echter niet zo: in dat geval is sprake van twee lokale variabelen (die toevallig dezelfde naam hebben), en het ophogen van de variabele in het ene frame heeft geen invloed op de waarde van de variabele in het andere frame!

Navigatie

Bij het definiëren van de frameset geven we door middel van het SRC-attribuut op, welk document initieel in elk van de frames wordt getoond. We willen echter in staat zijn om te navigeren, dat wil zeggen: om nieuwe documenten in de frames te tonen.

In HTML is dat erg eenvoudig. Indien een frame een document met een standaard link bevat (<A HREF="xyz.htm">) en de gebruiker klikt die aan, dan wordt het nieuwe document in datzelfde frame getoond; het vervangt het oude document. Door bij de link een TARGET-attribuut op te geven, kunnen we het document inladen in een ander frame (<A HREF="xyz.htm" TARGET="b">).

In JavaScript is het al even simpel. Het Window-object heeft namelijk een eigenschap "location" die de URL bevat van het document dat in het venster wordt getoond. Aangezien een frame een venster is, kunnen we aan deze eigenschap een nieuwe waarde toekennen, en zo een nieuw document inladen. We schrijven bijvoorbeeld: top.a.location="b.htm".

De eigenschap "location" is zelf weer een object met een aantal eigenschappen die verwijzen naar de verschillende onderdelen van de URL: href bevat bijvoorbeeld de hele URL, protocol geeft het gebruikte protocol (meestal "http:"), en search bevat de query string waarmee parameters kunnen worden doorgegeven van het ene scherm naar het andere (bijvoorbeeld "?naam=Jan").

Vanaf JavaScript versie 1.1 heeft location ook twee methoden: reload() waarmee het huidige document opnieuw wordt ingeladen, en replace() waarmee een nieuw document wordt ingeladen. Deze laatste methode is dus een alternatief voor het navigeren door middel van het toekennen van een nieuwe waarde aan "location". Het verschil is echter, dat bij gebruik van replace() de huidige URL in de nog te noemen History geheel wordt vervangen door de nieuwe URL.

Voor de volledigheid noemen we nog de mogelijkheid om het nieuwe document niet in een frame te tonen maar in een geheel nieuw browser-venster; in dat geval gebruiken we de volgende schrijfwijze:

window.open('xyz.htm', 'extra', 'toolbar=0, width=525, height=350, resizable=1, scrollbars=1')

Het nieuw geopende venster "extra" heeft dan een uiterlijk dat, als gevolg van onze parameters, afwijkt van een standaard browser-venster (het is kleiner en bevat geen toolbar). Indien een link wordt aangeklikt waarbij we een TARGET-attribuut hebben opgegeven met de naam van een niet-bestaand frame, dan wordt eveneens een nieuw venster geopend, maar dat heeft dan een standaard uiterlijk.

History

Elk venster en dus ook elk frame heeft een History-object dat we eveneens kunnen gebruiken om te navigeren. Een History-object is een lijst (om precies te zijn: een array) met daarin de URLs van de pagina's die in dat venster of frame zijn bekeken; het object is te vergelijken met de History die we kunnen opvragen via het menu van de browser zelf.

De twee belangrijkste methoden van het History-object zijn back() en forward() en deze gedragen zich zoals je zou verwachten: het venster of frame toont de vorige, respectievelijk de volgende URL uit de lijst --alsof we op de knoppen Back of Forward van de browser hadden gedrukt.

Er bestaat ook een methode go(), maar die werkt bij Netscape Navigator anders dan bij Internet Explorer, en geeft bovendien problemen bij documenten die zijn opgebouwd uit frames.

De methoden back() en forward() werken echter niet als de huidige URL reeds de eerste, respectievelijk de laatste uit de lijst is; het heeft dus alleen zin om forward() aan te roepen als we eerder al back() hebben aangeroepen.

Je zou wellicht verwachten dat we de waarde van elementen uit het History-array op kunnen vragen, net zoals we dat kunnen doen met de elementen van andere arrays. Omdat het hier echter gaat om potentieel gevoelige informatie (welke documenten heeft deze gebruiker zoal bekeken?), is dat normalerwijze niet mogelijk.

Een heel enkele browserversie staat dit wel toe, maar dan alleen als aan bepaalde beveiligingsvereisten wordt voldaan. Volgende keer zullen we uitgebreid ingaan op allerlei zaken die met beveiliging te maken hebben.

Conclusie

We hebben gezien hoe we in JavaScript omgaan met frames en met navigatie. In de volgende aflevering zullen we het hebben over cookies en beveiliging.