1 .
¿Qué cambia?
2 .
Escribir el código HTML de los menús
3 .
Cómo adaptar el código Javascript
3.1 .
Controlar el scroll
3.2 .
Sincronización
Parece que muchos de nuestros lectores desean tener, en un marco, las
opciones principales y, en otro, los menús desplegados. Os mostramos cómo hacerlo,
de una manera robusta.
¿Qué cambia?
Al contrario que el artículo anterior, este artículo no es una mejora
que todos los usuarios de los menús desplegables deban utilizar. Tan sólo es
recomendable para aquellos que quieran disponer de este tipo de menús en
páginas con varios marcos. Desde luego, es recomendable la lectura de
los anteriores artículos de esta serie para comprender éste.
El problema es el siguiente. Tenemos las opciones principales (es decir,
la barra de menús) en un marco, y los menús deben desplegarse en otro
distinto... Además, resulta conveniente no tener que estar escribiendo el
código HTML correspondiente a las opciones en cada uno de los documentos
en que se desplegarán, sino hacerlo sólo una vez en el marco que aloja
las barras de menús. De este modo es más sencillo de mantener y más difícil
cometer errores.
Escribir el código HTML de los menús
Si disponéis de Explorer 4 o Netscape 4 y superiores, podéis ver como
nuestro ejemplo.
El código no varía mucho, pero hay que hacer cambios. Entre otras cosas,
ahora parte del código está en una página y otra parte en otra. Debemos
tener cuidado de que ambos marcos estén ya totalmente cargados y cosas
así.
Prácticamente todo el código estará en el marco que contiene la
barra de menús desplegables, ya que será éste el que no cambie mientras
navegamos por nuestra página. Sin embargo, necesitará un par de "ayuditas"
del otro marco, que procuraramos sean lo más pequeñas posible.
Lo primero que debemos hacer es encontrar la manera de escribir el
código HTML de los menús desde el marco que contiene la barra pero no
los menús desplegados. A este marco lo llamaremos
opciones siendo el otro
principal. Esto lo conseguiremos, primero,
añadiendo la siguiente función:
function escribirMenus() {
top.principal.document.write('<DIV id="menu0" CLASS="menu">' +
'<A HREF="../../recursos/img.htm">Imágenes</A><BR>' +
...
'<A HREF="../131098.htm">No quedar encerrado en los marcos</A><BR>' +
'</div>');
}
Sin embargo, esta función deberá ser llamada desde el otro marco. Esto es
debido a la manera en que funciona document.write,
tema que ya está tratado en el
curso de Javascript,
por lo que no abundaremos en exceso en ello. Lo que pasa es que si queremos
escribir código HTML desde Javascript sin borrar el código que ya tiene la página,
deberemos hacerlo antes de que se termine de leer la página.
Es decir, llamando a document.write antes de que
aparezca enel código fuente de la página la etiqueta
</BODY>. Por tanto, colocaremos este código
justo antes de esa etiqueta:
...
<SCRIPT LANGUAGE="Javascript1.2">
<!--
if (top.opciones.escribirMenus)
top.opciones.escribirMenus();
else
history.go(0);
//-->
</SCRIPT>
</BODY>
</HTML>
Lo primero que hacemos es comprobar si la función que hemos creado un poco
más arriba ya existe. Si es así, la ejecuta, escribiendo el código HTML de
nuestros amores. Si no es así, vuelve a cargarse la página. Esto puede parecer
un desperdicio y, de hecho, lo es, pero es necesario ya que no tenemos manera de
"obligar" al navegador a que cargue el otro marco antes que éste. De todos modos,
no tendrá que recargarse muy a menudo, teniendo en cuenta que la función
escribirMenus() la colocaremos al principio del
fichero que contenga el otro marco y esta comprobación está al final del nuestro.
Por último, debemos incluir en el marco principal
la hoja de estilos correspondiente a nuestros menús. Ya que lo que deseamos
es poner el menor código repetido posible en esa página, lo mejor es guardar
en un archivo externo dicha hoja de estilo:
menus.css
.menu {
position:absolute;
visibility:hidden;
background-color: white;
layer-background-color: white;
color: black;
border-style: solid;
border-color: black;
border-width: 1px;
padding: 3px;
font-size : 12px;
font-family: "arial", "helvetica";
}
.menu A:hover {text-decoration: underline; color: blue;}
.menu A {text-decoration: none; color: black;}
Y, por supuesto, llamarla desde nuestra página:
<link rel="STYLESHEET" href="menus.css" type="text/css">
Cómo adaptar el código Javascript
Lo que hacemos es copiar y pegar el código que hicimos en el artículo
anterior y colocarlo en la página que contiene la barra de menús. Luego,
procederemos a modificarlo para que "comprenda" que los menús están ahora en
otro marco. Lo primero que vamos a hacer es crearnos una referencia al marco
donde se despliegan los menús para ahorranos el escribirlo muchas veces y no
tener que hacer muchos cambios si algún día le cambiamos el nombre:
var wref = parent.principal;
Lo siguiente será adaptar todas las referencias dentro del objeto menú
para que sean referencias a objetos situados en el otro marco. Esto se logra
cambiando una sola línea del código del constructor:
this.capaRefStr = (soporta.NS4) ?
'wref.document["'+capaID+'"]' :
((soporta.IE4) ? 'wref.document.all["'+capaID+'"]' : 'this.domRef');
Por último, o casi, nos queda hacer una pequeña labor de inicialización
del marco principal. Dado que los menús funcionan
desapareciendo cuando pulsamos en cualquier lugar de nuestras páginas, debemos
extender esa comprobación a todos los marcos que las componen. Quedaría un poco
feo que pulsáramos en el marco donde se despliegan las opciones y no pasara nada.
Por lo tanto, deberemos añadir el siguiente código en la cabecera de cada página
situada en dicho marco:
<SCRIPT language="javascript">
var wref = parent.opciones;
// Inicializacion
function inicializar() {
if (wref.soporta.DHTML) {
if (wref.soporta.NS4)
document.captureEvents(Event.MOUSEUP);
document.onmouseup = wref.ocultarMenuActivo;
}
}
window.onload = inicializar;
</SCRIPT>
Como vemos, es parte del código que hemos utilizado hasta ahora para
inicializar, pero adaptado al hecho de que todo nuestro código está en
un marco distinto, por lo que tenemos que colocar la referencia
wref delante de cada llamada a funciones
situadas en el marco opciones.
Controlar el scroll
Entre nuestros diversos problemas de adaptación, ahora tenemos uno nuevo,
¿qué pasa si el usuario intenta abrir un menú y la página situada en el marco
principal está desplazada por medio de una barra
de scroll? Pues que, como está colocada en un lugar fijo, seguramente se vea
sólo parte. Queda muy feo. Jo. Así que tendremos que buscarnos la vida para
colocarlo en el sitio adecuado. Para ello tendremos que utilizar tres soluciones
distintas.
La más sencilla de las tres nos viene dada por un estándar: el CSS2.
Desafortunadamente, la característica que vamos a utilizar sólo está
disponible en el Netscape 6PR1, así que tendremos que utilizar otra cosa tanto
para Explorer como para Netscape 4. Consiste en posicionar cada uno de los
menús no en forma absoluta (absolute) sino de
forma fija (fixed). Esto lo haremos en el
constructor de los menús:
if (soporta.DOM) {
...
if (!soporta.IE4)
this.domRef.style.position = "fixed";
}
Tenemos que colocar esta segunda condición, porque el Explorer 5 soporta
el DOM pero no esta característica del CSS2. En este navegador y el Netscape 4,
tendremos que utilizar dos propiedades que nos indican cuantos pixels está
desplazada la página de la esquina superior izquierda. En caso del Netscape
esas propiedades son pageXOffset y
pageYOffset. En el del Explorer
document.body.scrollLeft y
document.body.scrollTop. Así, de nuevo
en el constructor, creamos cuatro nuevas propiedades en el menú.
this.topOffsetStr = (soporta.NS4) ? 'wref.pageYOffset' :
(soporta.IE4 ? 'wref.document.body.scrollTop' : '0');
this.leftOffsetStr = (soporta.NS4) ? 'wref.pageXOffset' :
(soporta.IE4 ? 'wref.document.body.scrollLeft' : '0');
this.top = top;
this.left = left;
Las dos primeras cadenas contienen el código necesario para acceder
a esas propiedades en Netscape y Explorer y cero si es el Netscape 6.
Ahora modificamos la función que coloca el menú:
function cambiarPosicionMenu(top, left) {
eval(this.capaRefStr + this.estiloRefStr + this.topRefStr +
' = this.top + ' + this.topOffsetStr);
eval(this.capaRefStr + this.estiloRefStr + this.leftRefStr +
' = this.left + ' + this.leftOffsetStr);
}
Y ahora, debemos llamar a esta función desde los sitios adecuados.
El primer sitio donde lo haremos será la función
mostrarMenu(), de modo que así nos
aseguramos que, cada vez que mostremos un menú, lo hagamos en el
sitio adecuado. Ahora, como mejora adicional, intentaremos que los menús
se coloquen bien cada vez que se muevan las barras de scroll. Para ello
creamos la siguiente función:
function moverMenuActivo(e) {
if (menuActivo)
menuActivo.cambiarPosicion();
}
Y la llamamos desde la función de inicialización:
function inicializar() {
if (wref.soporta.DHTML) {
if (wref.soporta.NS4)
document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
document.onmouseup = wref.ocultarMenuActivo;
document.onmousemove = wref.moverMenuActivo;
}
}
Sincronización
Lo que hemos hecho funcionaría a la perfección si vivieramos en un Internet
de velocidad brutal donde acceder a una página fuera lo mismo que leerla del disco
duro. Pero ya que no es así tenemos que lidiar con un problema adicional: la
sincronización. Esto significa, en la práctica, que el código que maneja los menús
debe saber de antemano que los dos marcos se han terminado de cargar.
Para lograrlo, lo primero que debemos decidir es cual de los dos marcos
se encargara de este trabajo. Sólo puede haber una respuesta. Uno de los marcos
se queda fijo, el de la barra de menús, y el otro varía, que es el principal.
Lo más cómodo será que éste último controle lo que pasa en el fijo, que
normalmente estará ya cargado y no nos causará mayores problemas. Lo haremos de
la siguiente manera:
function inicializar() {
if (!wref.inicializar) {
setTimeout("inicializar()",10);
return;
}
if (wref.soporta.DHTML) {
if (wref.soporta.NS4)
document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
document.onmouseup = wref.ocultarMenuActivo;
document.onmousemove = wref.moverMenuActivo;
wref.inicializar();
}
}
window.onload = inicializar;
La función de inicialización del marco
principal se ejecuta cuando éste termina de
cargar. Si el código fuente del otro marco (colocaremos la función
inicializar() la última) no está aún cargado,
esperamos a que lo esté. Y cuando eso sucede inicializamos también
el marco de opciones con la llamada
a wref.inicializar().
Pero existe un último problema. Y de verdad que éste sí que es el último.
En los intervalos en los que se pulsa un enlace y el marco que contiene
la barra de menús está cargado e inicializado pero en el otro todavía
no se ha cargado ninguna página, tendremos un error de Javascript si intentamos
desplegar un menú. Para evitarlo colocamos la siguiente comprobación:
function mostrarMenu() {
if (!eval(this.capaRefStr)) return;
...
}
function ocultarMenu() {
if (!eval(this.capaRefStr)) return;
...
}
Comprobamos si existe el menú y, si no es así, no hacemos nada. Y esto
es todo, amigos.