Els menús, de tots tipus, mides i colors, són una part de la interfície dels programes que ens resulta molt familiar. Això fa que siguin un recurs oportú per a les pàgines web, on l'usuari entra per primer cop sense saber què es trobarà. Si li oferim solucions que reconegui a primera vista, la visita li resultarà més efectiva i gratificant.

S'anomenen "persianes" les capes en moviment que aparèixen, generalment, en sentit vertical. Poden contenir qualsevol element, també un menú. La seva utilitat es fonamenta en un millor aprofitament de l'espai. També en la possibilitat de mantenir una informació amagada i mostrar-la només quan es necessita. Finalment, tenen una funció estètica i donen imatge de dinamisme.

  Conceptes JavaScript en aquest capítol
Mètode split() : crea un Array a partir d'una cadena, partint-la pel caràcter especificat.
Propietat innerHTML : assigna el contingut d'una capa de forma dinàmica. Només iExplorer

  1.- Menú amb rollover
Volem fer un menú on cada línia comenci per una imatge que es substitueixi per una altra quan passa el ratolí. Això ho vam veure a la pràctica "Imatges i scripts". Ara, però, ho volem automatitzar amb una mica de JavaScript.

De fet, és el mateix menú que vam fer a l'exercici del mòdul 2, i el podem prendre com a base. Com aleshores, comencem fent un Array que tractarem com a parells adreça - títol. Fins aquí és el mateix. Les diferències comencen al bucle.

A cada volta, definim els events onMouseOver i onMouseOut de manera que canviïn la propietat .src d'una imatge. Quina? Doncs, muntem un nom, per exemple, a base d'una lletra i l'índex actual del bucle. És clar que també podem fer precàrrega i utilitzar una funció d'intercanvi, com hem vist a la pràctica 1. Això ho deixem a l'elecció de cadascú.

Ja que hi som, també redefinim l'event onClick perquè ens tregui el marquet de punts del vincle. Després posem la imatge per defecte, amb el seu nom (tal com hem fet abans), l'adreça i les mesures. Finalment, escrivim el vincle i tanquem la fila.

<script language="javascript">
var menu = new Array(

var i, url, nom
for ( i=0; i<menu.length; i+=2 ) {
  url = menu[i]
  nom = menu[i+1]
  with( document ) {
    writeln( '<tr><td class="menu"><a href="' + url + '"')
    writeln( 'onMouseOver="m' + i + '.src=\'images/bola2.gif\'"' )
    writeln( 'onMouseOut="m' + i + '.src=\'images/bola1.gif\'"' )
    writeln( 'onClick="window.focus()">')
    writeln( '<img name="m' + i + '" src="images/bola1.gif" border="0"' )
    writeln( 'width="12" height="12" hspace="4">' )
    writeln( nom + '</a></td></tr>' )
  }
}
</script>
 
2.- Esquema desplegable
Com a segon plat, prepararem la base del que podria arribar a ser un menú més complet: un esquema desplegable. Per tal d'entendre millor l'algoritme, el deixarem pelat. Si algú s'anima i ho vol combinar amb intercanvi d'imatges i amanir-ho amb una mica de bon gust a l'hora de dissenyar els estils, pot quedar força bé.

Comencem aplicant la lògica a la construcció d'un Array. Cada element serà una carpeta i, com que té una certa complexitat, els declararem d'un en un. A la carpeta, que tindrà un títol, hi haurà els submenús, formats per parells adreça - nom. Utilitzarem el símbol punt i coma (;) per separar submenús i la barra vertical per separar els elements del parell:

  carpeta[0] = 'Primer menú ; sub1.htm | Primer vincle ; sub2.htm | Segon vincle ; ...'

És clar que, si ho escrivim en línies separades, s'entendrà millor. De moment, cada element té el títol i els parells. Si volem més submenús només ens cal afegir (o inserir) més parelles. També necessitem un Array, paral·lel a l'anterior, que recordi l'estat de cada carpeta. El declarem i omplim de valors false. D'entrada, totes les carpetes queden tancades:

  var obert = new Array()
  for ( i=0; i<carpeta.length; i++ ) {
    obert[i] = false
  }


Ja tenim les dades. Ara necessitem una funció per processar-les. obreTanca(n) rep com a paràmetre el número de carpeta. Més endavant farem que els títols de les carpetes siguin vincles cap a aquesta funció. Això vol que, quan s'inicia la funció, és perquè s'ha fet clic sobre una carpeta i, en conseqüència s'ha de canviar d'estat:  obert[n] = ! obert[n]

A continuació declarem la variable txt buida. L'anirem omplint de codi HTML que, en acabar, bolcarem en una capa. De moment, ja sabem què toca: engegar un bucle que faci el recorregut per les carpetes. A cada volta, es troba amb una autèntica "botifarra" de dades, que haurem de partir pel lloc correcte. Com? Localitzant els caràcters separadors, els punt i coma: var item = carpeta[i].split(';')

El mètode split() crea un Array a partir d'una cadena, utilitzant com a "punts de tall" el caràcter que li especifiquem. En el cas que ens ocupa, ara tenim un array "ítem" de tres elements: el primer, ítem[0], és el nom de la carpeta i, els altres, les parelles de submenús. Haurem d'afegir al text (variable txt) de la capa el vincle per obrir i tancar la carpeta:

  txt += '<b><a href="javascript:obreTanca(' + i + ')">' + item[0] + '</a></b><br>'

Si la carpeta és oberta, fem un altre bucle que vagi repassant les parelles, les parteixi per on trobi la barra vertical i faci els enllaços. Comencem per l'índex 1 (el 0 era la carpeta):

   for ( j=1; j<item.length-1; j++ ) {
      var url = item[j].split( '|' )
      txt += '&nbsp; &nbsp;<a href = "' + url[0] + '">' + url[1] + '</a><br>'
   }


Un cop acabats els bucles hem d'escriure el text generat a la capa. Ja sabem que els dos exploradors ho fan diferent (mireu el codi). Finalment, hem d'activar el sistema al <body> quan es carrega la pàgina (onLoad), amb el número del menú que vulguem obrir per defecte, i crear una capa per posar el menú. Tindrà, evidentment, l'identificador que hem utilitzat per escriure el text que ha generat la funció, en aquest cas, id="menu".

<script language="javascript">
var carpeta=new Array()
carpeta[0]='Primer menú;'+
    'sub11.htm|Primer submenú;'+
    'sub12.htm|Segon submenú;'
carpeta[1]='Segon menú;'+
    'sub21.htm|Submenú 2.1;'+
    'sub22.htm|Submenú 2.2;'+
    'sub23.htm|Submenú 2.3;'
var obert = new Array()
for ( i=0; i<carpeta.length; i++ ) {
  obert[i] = false
}
function obreTanca(n) {
  obert[n] = !obert[n]
  var txt=''
  for ( i=0; i<carpeta.length; i++ ) {
    var item = carpeta[i].split(';')
    txt += '<b><a href="javascript:obreTanca(' + i + ')">' + item[0] + '</a></b><br>'
    if ( obert[i] ){
      for ( j=1; j<item.length-1; j++ ) {
        var url = item[j].split('|')
        txt += '&nbsp; &nbsp;<a href="' + url[0] + '">' + url[1] + '</a><br>'
      }
    }
  }
  if (document.all) menu.innerHTML=txt
  else {
    document.menu.document.write(txt)
    document.menu.document.close()
  }
}
</script>
</head>

<body onload="
obreTanca(999)">
<div id="menu" style="position:absolute;top:20px;left:30px"></div>
...


A l'exemple podem veure una combinació d'aquest exercici i l'anterior, és a dir, un esquema desplegable amb rollover. Mireu el codi font per a més detalls.
 
3.- Persiana
(Internet Explorer)
Farem un menú amb l'estil del d'inici dels Windows, en una capa que s'obre cap amunt. Haurem de combinar scripts, css i HTML i només funcionarà amb Internet Explorer. Les diferències entre tots dos navegadors, en el tractament d'estils i capes, són enormes. Si volem compatibilitat, és més pràctic i curt escriure dos arxius diferents. A més, el codi de cadascun ja és prou complex, de manera que, així, l'entendrem millor.

Al cos de la pàgina farem una capa amb identificador <div id="Layer1">, hi escriurem els vincles i la tancarem. A sota, en una altra capa, hi posarem un formulari amb un parell de botons que pujaran la persiana a diferents velocitats.

  <input type="button" value=" Lent " onClick="Persiana( true, 'Layer1' , 2 )">

En el full d'estils establim la lletra del cos (body) - Explorer ho permet -, el color i decoració dels vincles (a), el comportament rollover (a:hover) i les característiques de la capa (#Layer1): posició absoluta, 115x115, z-índex 1, visibilitat oculta, bordes amb relleu, fons gris, lletra de 14, interlineat de 20 i marge intern de 5, tot píxels.

Fixem-nos en el coixinet ( # ) que utilitzem per definir els estils de la capa. Quan s'utilitza aquest signe, css interpreta que ens referim a una etiqueta concreta (un objecte) i no una classe.

I ara, JavaScript. Comencem declarant una variable global buida: (var obj='') i, a continuació, la funció persiana, que, com hem vist, rebrà tres paràmetres: estat, identificador de capa i velocitat. A dins, haurem d'actuar segons l'estat (variable "inici").

Quan hem d'inicialitzar la capa, l'assignem a la variable global "obj" i li afegim propietats:
  - obj.y contindrà l'altura de la capa i ens servirà per fer càlculs.
  - obj.clp serà la zona que es mostra, en forma de tant per cent. L'iniciem a zero.
  - obj.sub, establert en 140, és la coordenada vertical d'inici de la capa, el seu "borde" superior.
  - obj.incr serà l'increment, que arrodonim multiplicant altura per velocitat i dividint per cent.

Si la capa ja estava inicialitzada, el que toca és incrementar la zona que es mostra - utilitzant "obj.incr" - i vigilar que no passi de 100.

Un cop acabada aquesta distinció, redefinim la zona visible (obj.style.clip) a partir del valor de "obj.clp", en forma de %. També calculem la posició superior d'inici de la capa:

  obj.style.posTop = Math.round( obj.sup - obj.y * obj.clp / 100 )

Si estàvem inicialitzant la capa, ara és el moment de fer-la visible. Finalment, comprovem si "obj.clp" és encara menor que 100. Si ho és, fem un temporitzador que torni a cridar la funció, amb el paràmetre inici a "false" i els altres dos, buits. Si, en canvi, "obj.clp" ja no és menor que 100, el posem a zero i acabem la funció

<script language="JavaScript">
var obj=''
function Persiana( inici, capa, vel ) {
  if ( inici ) {
    obj = document.all[capa]
    obj.y = obj.offsetHeight
    obj.clp = 0
    obj.sup = 140
    obj.incr = Math.round( obj.y * vel / 100 )
  }
  else {
    obj.clp += obj.incr
    if ( obj.clp > 100 ) obj.clp = 100
  }
  obj.style.clip = 'rect( auto, auto, '+ obj.clp+'%, auto )'
  obj.style.posTop = Math.round( obj.sup - obj.y * obj.clp / 100 )
  if ( inici ) obj.style.visibility = 'visible'
  if ( obj.clp < 100 ) setTimeout( 'Persiana( false, "", "")', 5 )
  else obj.clp = 0
}
</script>
<style type="text/css">

body { font-family: Arial, Helvetica, sans-serif}
a { color: #000000; text-decoration: none}
a:hover { color: #990000; font-weight: bold; text-decoration: underline}
#Layer1 { position: absolute; width: 115px; height: 115px; z-index: 1;visibility: hidden;
border: 1 solid black; border-left: 1 solid #FFFFFF; border-top: 1 solid #FFFFFF;
background:#D0D0C8; font-size: 14px; line-height: 20px; padding: 5px}

</style>
</head>

<body ... >
<div id="Layer1">
<a href="enlloc1.htm" target="_blank"><b>P</b>rimer vincle</a><br>
<a href="enlloc2.htm" target="_blank"><b>S</b>egon vincle</a><br>
<a href="enlloc3.htm" target="_blank"><b>T</b>ercer vincle</a><br>
<a href="enlloc4.htm" target="_blank"><b>Q</b>uart vincle</a><br>
<a href="enlloc5.htm" target="_blank"><b>C</b>inquè vincle</a>
</div>
<div style="position:absolute; top: 144px">
<form name="f1">
<input type="button" value=" Lent " onClick="
Persiana( true, 'Layer1', 2 )">
<input type="button" value=" Ràpid " onClick="
Persiana( true, 'Layer1', 8 )">
</form>
</div>
...