El concepto es muy similar al expuesto en el tutorial de mostrar y ocultar información. Si bien se podría usar el mismo sistema y código vamos a hacer nuestro menú contráctil de una forma diferente con el fin de aprender otras formas menos intrusivas de hacerlo.
Partimos de que tenemos un menú formado por una lista no ordenada
(ol)de links categorizados bajo distintos apartados:
El código HTML es el siguiente
<ul>
<li>Apartado 1
<ul>
<li>sección 1</li>
<li>sección 2</li>
<li>sección 3</li>
</ul>
</li>
<li>Apartado 2
<ul>
<li>seccion 4</li>
<li>sección 5</li>
</ul>
</li>
<li>Apartado 3
<ul>
<li>sección 6</li>
<li>sección 7</li>
<li>sección 8</li>
</ul>
</li>
</ul>
Nos vamos a ahorrar el asignar identificadores y andar enviándolos
a la función Javascript que nos maneje el asunto a base de manejarnos
puramente usando el DOM.
Cada vez que se llame a la función Javascript enviaremos como único
parámetro el propio elemento que la llama usando la sentencia this
que lo que hace es referenciarse o indicarse a sí mismo el objeto
que dispara el evento.
Si a nuestra función la llamamos Menu(elementoQueLlama)
y debemos pasarle el propio elemento que la llama como argumento lo haremos
usando this
<ul>
<li onclick="Menu(this)"> Apartado 1
<ul> ...
Y en nuestra función Javascript haremos lo siguiente: lo que queremos
es mostrar u ocultar la lista anidada (ul) dentro del elemento
del Apartado 1
<ul>
<li onclick="Menu(this)"> Apartado 1
<ul> ... // ◄ -- este elemento queremos mostrar u ocultar
Aprovechando que todos los elementos donde hacer click y los elementos
a ser mostrados u ocultados van a tener un patrón estructural común
encontraremos al elemento sobre el cual actuar a partir del elemento que
activa la llamada a la función y que se referencia a sí mismo
mediante this
Observamos que el ul que queremos abrir/cerrar está
anidado dentro del elemento li que llama a la función,
es decir, es un nodo dentro del elemento li que llama
a la función.
ul
li con la llamada a Menu(this)
ul a mostrar u ocultarEl nodo ul a mostrar u ocultar (el tercero del esquema) es
un nodo hijo del nodo li con la llamada a
Menu(this) (el segundo del esquema).
El nodo li con la llamada a Menu(this) (el segundo
del esquema) es el nodo padre del nodo ul
a mostrar u ocultar.
Si a la función Javascript Menu(elementoQueLlama) le
hacemos una llamada enviando la referencia del propio elemento que la llama
como argumento con Menu(this) tendremos dentro de la función
la referencia al elemento que la llamó dentro de la variable elementoQueLlama
function Menu(elementoQueLlama){
// elementoQueLlama guarda ahora al elemento que llamó a la función
}
¿Y cuál es el elemento que queremos abrir o cerrar? El elemento
ul que es un nodo hijo de elementoQueLlama
Vamos a encontrar, lo primero, los elementos ul contenidos
en elementoQueLlama usando el método del DOM
getElementsByTagName()
elementosUlEnelementoQueLlama = elementoQueLlama.getElementsByTagName('ul')
Ahora tenemos en la variable elementosUlEnelementoQueLlama la
lista de ul's que se encuentren dentro del elemento que llamó
al script guardados en un array de 0 o más elementos (según
lo que se haya encontrado dentro del elemento al que se le hizo click).
Este elemento ul que buscamos será el primer ul
definido en elementoQueLlama así que accedemos a él
mediante su posición en el array devuelto
elementoQueQueremos = elementosUlEnelementoQueLlama[0];
Bueno, pues ya tenemos localizado el elemento que queremos abrir o cerrar
en elementoQueQueremos. Ahora no tenemos más que asignarle
la propiedad CSS display
a none o a block. Vamos a usar los operadores
condicionales para hacerlo:
elementoQueQueremos.style.display = elementoQueQueremos.style.display
== 'none' ? 'block' : 'none' ;
Estupendo, no nos queda más que probarlo. Agregamos como atributo
el manejador de eventos a los elementos li que deban encargarse
de activar el script con la llamada a la función onclick="Menu(this)"
<li onclick="Menu(this)">Apartado 1 ...
¿Qué pasa si queremos poner otro nivel de profundidad a nuestro menú? Pues sencillamente, que hará cosas raras.
Nos encontramos con que al intentar abrir o cerrar Sección 1 lo que se activa además es el Apartado 1, es decir, el nodo padre de Sección 1.
¿Por qué sucede esto?
Pues es muy sencillo. El elemento li padre (Apartado
1) abarca a todo su contenido; eso incluye la lista anidada y la
lista anidada dentro de ésta. De hecho en cualquier parte del área
de Apartado 1 (eso incluye márgenes) se nos disparará
la llamada a la función en el momento de hacer click. Así
que cuando hacemos click en Sección 1 su elemento
padre también está recogiendo el evento.
Lo que haremos entonces es cambiar el manejador de eventos a otro elemento que sólo afecte al texto en donde se deba hacer click.
Vamos a meter el texto clickable dentro de un tag strong.
Podemos usar cualquier otro tag; si no queremos agregarle ningún
sentido semántico a ese texto podemos usar un span pero
en este ejemplo usaremos strong.
<ul>
<li><strong onclick="Menu(this)">Apartado 1</strong>
<ul>
<li><strong onclick="Menu(this)">sección 1 </strong>
<ul>
<li>seccion 1-b</li>
</ul>
</li>
...
Macanudo, ahora el evento sólo afecta al texto en donde se hará click y no al resto de elementos hijos o padres.
Pero … ahora el script no funciona :-(
Pasa que estamos usando esta referencia
elementosUlEnelementoQueLlama = elementoQueLlama.getElementsByTagName('ul')
y ahora el elementoQueLlama ya no es el li, si
no un tag strong hijo de este li y éste
strong no contiene a ningún elemento ul,
por lo tanto nos devuelve un array (lista) vacío.
La forma de solucionarlo es referenciar al li que nos interesa
(el que teníamos referenciado en un principio). Para ello nada más
fácil que indicar que queremos al padre del elementoQueLlama
elLiQueQueremos = elementoQueLlama.parentNode
Resumiendo el único cambio que debemos hacer a nuestra línea es este:
elementosUlEnelementoQueLlama = elementoQueLlama.parentNode.getElementsByTagName('ul')
¡Listo! Ya tenemos de nuevo referenciado nuestro elemento para abrir
o cerrar. Eso sí, algo importante es que el script funciona en base
a un patrón estructural del menú; eso quiere decir que todos
los elementos deben estar estructurados de la misma manera, o sea, llamando
a la función desde algún tag que implique sólo al texto
en donde hacer click y que ese tag sea hijo directo (no nieto) del li
padre del ul con el que queramos interactuar.
function Menu(elementoQueLlama){
elementosUlEnelementoQueLlama = elementoQueLlama.parentNode.getElementsByTagName('ul');
elementoQueQueremos = elementosUlEnelementoQueLlama[0];
elementoQueQueremos.style.display = elementoQueQueremos.style.display == 'none' ? 'block' : 'none' ;
}
Ahora bien, eso ha estado bien para describir el ejemplo. Yo le pondría unos nombres más sencillos a esas variables :-P y podríamos reducir a sólo dos líneas así
function Menu(el){
elemento = el.parentNode.getElementsByTagName('ul')[0]
elemento.style.display = elemento.style.display == 'none' ? 'block' : 'none'
}