> Manuales > Taller de jQuery

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:

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.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual