Menú desplegable en jQuery

  • Por
Creamos un plugin jQuery para implementar una barra de navegación con un menú desplegable en varios submenús.
Cuando tratamos con interfaces de usuario en páginas web uno de los ejemplos más típicos es el menú desplegable, osea, una barra de navegación que muestra varios enlaces y que, al ponerse encima de esos enlaces, muestra un submenú con otros enlaces. Ese submenú que aparece automáticamente, también desaparece por si solo cuando el usuario le retira el puntero del mouse.

Si lo deseas, puedes ver el menú desplegable dinámico que vamos a realizar.

En diversos artículos de DesarrolloWeb.com hemos tratado sobre la realización de menús desplegables y en esta ocasión vamos a dedicarnos a realizar el sistema utilizando jQuery, por medio de un plugin que cualquier desarrollador pueda utilizar en su propia página web.

Claro que éste no es un problema trivial, puesto que tenemos que trabajar con varias cosas de aquí y de allá: CSS, Javascript y jQuery para realizar el plugin. Afortunadamente tenemos todas las referencias en DesarrolloWeb.com para aprender todo lo que vamos a necesitar.

Objeto para configurar las opciones del menú

Para realizar este ejercicio de la manera más versátil posible hemos implementado una notación para generar las opciones del menú en una variable Javascript de tipo objeto. Es decir, los menús y submenús se especificarán con Javascript y enviaremos ese objeto al plugin para generar el HTML necesario para crearlos en la página.

Así que esta es la variable donde definimos el contenido del navegador desplegable:

//un array por cada uno de los menús desplegables
var opciones_menu = [
   {
      texto: "Enlace 1",
      url: "http://www.desarrolloweb.com",
      enlaces: [
         {
            texto: "Enlace 1.1",
            url: "#Enlace1-1"
         },
         {
            texto: "Enlace 1.2",
            url: "#Enlace1-2"
         },
         {
            texto: "Enlace 1.3",
            url: "#Enlace1-3"
         }
      ]
   },
      
   {
      texto: "DesarrolloMultimedia.es",
      url: "http://www.desarrollomultimedia.es",
      enlaces: [
         {
            texto: "Enlace 2.1",
            url: "#Enlace2-1"
         },
         {
            texto: "Enlace 2.2",
            url: "#Enlace2-2"
         }
      ]
   }   
];

Como se puede ver se ha definido un array donde tenemos cada uno de los enlaces principales, que a su vez se declaran con un objeto que tiene varias propiedades.

Para cada enlace principal tenemos los datos:

  • texto: con una cadena para el texto del enlace
  • url: con una cadena para la URL a la que dirigir el enlace
  • enlaces: un array con cada uno de los enlaces del submenú asociado a este enlace principal. Los enlaces del submenú que colocamos en este array son otros objetos. Con los siguientes datos:
    • texto: el texto para el enlace del submenú
    • url: la URL a la que dirigir este enlace del submenú.

Una vez entendido ese formato para el objeto de los enlaces, podemos ver el código del plugin.

////////////////////////////////////////////////////////////////////////////
//creación del plugin generaMenu.
//envío el menú de opciones como parámetro
////////////////////////////////////////////////////////////////////////////
(function($) {

$.fn.generaMenu = function(menu) {
   this.each(function(){
      var retardo;
      var capaMenu = $(this);
      //creo e inserto la lista principal
      var listaPrincipal = $('<ul></ul>');
      capaMenu.append(listaPrincipal);
      //enlaces principales
      var arrayEnlaces = [];
      var arrayCapasSubmenu = [];
      var arrayLiMenuPrincipal = [];
      //recorro los elementos del menú
      jQuery.each(menu, function() {
         //ahora en this tengo cada uno de los elementos.
         var elementoPrincipal = $('<li></li>');
         listaPrincipal.append(elementoPrincipal);
         //creo el enlace e inserto
         var enlacePrincipal = $('<a href="' + this.url + '">' + this.texto + '</a>');
         elementoPrincipal.append(enlacePrincipal);
         
         var capaSubmenu = $('<div class="submenu"></div>');
         //guardo la capa submenu en el elemento enlaceprincipal
         enlacePrincipal.data("capaSubmenu",capaSubmenu);
         //creo una lista para poner los enlaces
         var subLista = $('<ul></ul>');
         //añado la lista a capaMenu
         capaSubmenu.append(subLista);
         //para cada elace asociado
         jQuery.each(this.enlaces, function() {
            //en this tengo cada uno de los enlaces
            //creo el elemento de la lista del submenú actual
            var subElemento = $('<li></li>');
            //meto el elemento de la lista en la lista
            subLista.append(subElemento);
            //creo el enlace
            var subEnlace = $('<a href="' + this.url + '">' + this.texto + '</a>');
            //cargo el enlace en la lista
            subElemento.append(subEnlace);
            
         });
         //inserto la capa del submenu en el cuerpo de la página
         $(document.body).append(capaSubmenu);
         
         
         /////////////////////////////////////////
         //EVENTOS
         /////////////////////////////////////////
         
         //defino el evento mouseover para el enlace principal
         enlacePrincipal.mouseover(function(e){
            var enlace = $(this);
            clearTimeout(retardo)
            ocultarTodosSubmenus();
            //recupero la capa de submenu asociada
            submenu = enlace.data("capaSubmenu");
            //la muestro
            submenu.css("display", "block");
         });
         
         //defino el evento para el enlace principal
         enlacePrincipal.mouseout(function(e){
            var enlace = $(this);
            //recupero la capa de submenu asociada
            submenu = enlace.data("capaSubmenu");
            //la oculto
            clearTimeout(retardo);
            retardo = setTimeout("submenu.css('display', 'none');",1000)
            
         });
         
         //evento para las capa del submenu
         capaSubmenu.mouseover(function(){
            clearTimeout(retardo);
         })
         
         //evento para ocultar las capa del submenu
         capaSubmenu.mouseout(function(){
            clearTimeout(retardo);
            submenu = $(this);
            retardo = setTimeout("submenu.css('display', 'none');",1000)
         })

         //evento para cuando se redimensione la ventana
         if(arrayEnlaces.length==0){
            //Este evento sólo lo quiero ejecutar una vez
            $(window).resize(function(){
               colocarCapasSubmenus();
            });
         }
         
         /////////////////////////////////////////
         //FUNCIONES PRIVADAS DEL PLUGIN
         /////////////////////////////////////////
         
         //una función privada para ocultar todos los submenus
         function ocultarTodosSubmenus(){
            $.each(arrayCapasSubmenu, function(){
               this.css("display", "none");
            });
         }
         
         //función para colocar las capas de submenús al lado de los enlaces
         function colocarCapasSubmenus(){
            $.each(arrayCapasSubmenu, function(i){
               //coloco la capa en el lugar donde me interesa
               var posicionEnlace = arrayLiMenuPrincipal[i].offset();
               this.css({
                  left: posicionEnlace.left,
                  top: posicionEnlace.top + 28
               });
            });
         }
         
         
         //guardo el enlace y las capas de submenús y los elementos li en arrays
         arrayEnlaces.push(enlacePrincipal);
         arrayCapasSubmenu.push(capaSubmenu);
         arrayLiMenuPrincipal.push(elementoPrincipal);
         
         //coloco inicialmente las capas de submenús
         colocarCapasSubmenus();
      });
      
   });
   
   return this;
};

})(jQuery);

Como se puede ver, gran parte del código es para hacer unos bucles por los que recorrer todo el array de enlaces que pasamos como parámetro al plugin y construir el HTML necesario para el menú.

Luego se crearon unos eventos para mostrar y ocultar los submenús al situarse encima de los enlaces.

Uno de los temas a destacar en este código es que las capas de los submenús se tienen que colocar al lado de los enlaces principales. Para ello, se utiliza la función colocarCapasSubmenus(), que recorre el array de capas del submenú y coloca una a una todas estas capas en una posición absoluta que se calcula en función de la posición del elemento LI donde está el enlace principal.

Nota: Aunque el plugin sea algo complejo, no vamos a ofrecer más explicaciones sobre por qué se ha hecho así, aparte de los propios comentarios del código. Si deseas encontrar alguna que otra explicación adicional, te recomiendo que leas los artículos Menú desplegable Cross-Browser 1 y la segunda parte del menú desplegable cross-browser, que utilizan exactamente la misma idea básica para ser construidos que este menú desplegable en jQuery.

Ejecutar el plugin para generar el menú desplegable

Ahora sólo nos queda invocar al plugin para generar el menú desplegable. Para eso necesitamos una capa donde mostrar el menú en el código HTML.

<div id="menu"></div>

Además, necesitamos el array con los enlaces del menú, que habíamos definido al principio del artículo en la variable "opciones_menu". Esa variable la tenemos que enviar al iniciar el plugin.

$("#menu").generaMenu(opciones_menu);

Con esto ya tenemos el menú funcionando en la página. Sólo nos faltaría un poco de CSS.

CSS para definir el aspecto del menú desplegable

Por último vamos a decorar el menú según nuestros intereses, para que quede acorde con el diseño de nuestra página.

Para definir los estilos de los enlaces del menú principal, lo podemos hacer a partir de la capa donde estamos mostrando el menú, a la que habíamos puesto en le código HTML un identificador id="menu". A través de ese identificador podremos definir no sólo el estilo de la propia capa, sino también el estilo de la lista UL que se generará para el menú y de los elementos LI o los enlaces.

#menu{
   background-color: #e5e5e5;
   padding: 10px;
   text-align: center;
   overflow: hidden;
}
#menu ul, .submenu ul{
   list-style: none;
   margin: 0;
   padding: 0;
}
#menu li{
   margin: 0;
   padding: 0 20px 0 10px;
   float: left;
}

También podemos estilizar cada una de las capas de los submenús, a las que en el código del plugin le pusimos la clase CSS "submenu". Ese nombre de clase es fijo en el código de nuestro plugin, por lo que una mejora sería que el plugin recibiera el nombre de esa clase en un array de opciones, para que cada usuario lo pudiera configurar.

Como de momento ese nombre de clase no se puede cambiar, tendríamos que aplicar los estilos de esta manera.

.submenu{
   display: none;
   position: absolute;
   padding: 4px 4px 2px;
   background-color: #e5e5e5;
}
.submenu li{
   padding: 7px 10px;
   margin: 4px 0;
   background-color: #ffc;
   width: 200px;
}
.submenu li:hover{
   background-color: #ff6;
}

Como se puede ver, no sólo se aplican estilos a la capa del submenú, sino también a las listas UL que contendrá, sus elementos LI, enlaces, etc.

Así que no hay mucho más que contar sobre este menú desplegable. Seguramente a los lectores se les ocurrirá varias mejoras, pero de momento está bien tal como nos ha quedado y no deseamos complicar aun más el ejercicio.

Si deseas ver el menú desplegable hasta el momento, puedes hacerlo desde este enlace al ejemplo en funcionamiento.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir

Comentarios

Borjus

21/7/2010
MAL FUNCIONAMIENTO DEL MENU
Buenos días.He estado probando el ejemplo en Firefox 3.6.4 y los submenús no se cierran al quitar el ratón sobre ellos.

NETkoholik

21/7/2010
escape al menu
Es tambien el mismo comentario que iba a añadir.. No es facil salirse del menu emergente (o desplegable). No funciona cliqueando fuera, no funciona con Esc (engorroso ya de por si), no funciona simplemente apartando el puntero. Funciona aparentemente moviendo el puntero fuera y esperando unos segundos. Algo poco practico.

midesweb

22/7/2010
Faltaba un evento para ocultar los submenús
Hola!!

Gracias a las dos personas que han comentado este artículo!! efectivamente, el ejemplo no funcionaba del todo bien y es que faltaba un detalle por realizar en el menú desplegable para que funcionase perfectamente.

Era simplemente el evento onmouseout, que hay que definir en las capas de los submenús, para ocultarlas cuando el ratón se retira de encima de ellas. Se me olvidó escribirlo en el código de este plugin jQuery. Un despiste mío.

La verdad es que no era nada demasiado dificil de ver, si se ha seguido el manual de jQuery que hemos publicado en DesarrolloWeb.com, pero claro que merece la pena publicar bien el ejercicio y hacerlo completo. Así que he actualizado tanto el código del plugin en el texto del artículo, como el propio ejemplo en funcionamiento.

Gracias de nuevo por ayudarnos a mantener y corregir los artículos que publicamos. Cualquier otro comentario será bienvenido. Aviso también que en el futuro tengo la intención de mejorar un poco este ejemplo para incorporar algún efecto especial que de un poco más de vistosidad a este script para implementar un navegador dinámico con submenús desplegables.

Un saludo!

Larry

20/8/2010
Mantener visible el menú
Saludos. Muy interesante tu desarrollo, pero cuando llamo a otra página, necesito que el menú se mantenga visible. No puedo utilizar frames ya que la linea que divide con el frame inferior no permite que los submenus se muestren completos. Agradeceré tus opiniones.

Larry