Hay un millón de formas distintas de crear menús deplegables con HTML. Unas sólo utilizan CSS y selectores :hover, otras utilizan javascript y otras una combinaación de ambas.

Siempre podemos buscar y utilizar algunos de los muchos scripts que hay por la red peroyo prefiero tener mi propia colección de scripts y plugins sencillos. Esto me permite, por un lado, aprender como funcionan las cosas y, por otro, tener un arsenal de sencillas herramientas, rápidas de comprender y sencillas de utilizar.

Dicho esto, vamos con nuestro plugin para menú deplegables:

Demo

Haz click aquí para verlo en acción.

HTML

Los menús deben estar incluidos en una lista desordenada, y los submenus deben ser a su vez listas desordenadas.

Los puntos clave son:

  • Los menos deben estar incluidos en una lista desordenada ul
  • Los submenus deben ser a su vez listas desordenadas
  • El enlace que activa el menú deplegable debe tener la clase trigger
  • El menú desplegable debe tener la clase "submenu"

Aquí podemos ver la estructura básica:

<ul id="menu">
  <li>
    <a href="#" class="trigger">Coches</a>
    <ul class="submenu">
      <li><a href="#">Ferrari</a></li>
      <li><a href="#">Mercedes</a></li>
      <li><a href="#">Renault</a></li>
    </ul>
  </li>
</ul>

CSS

El único CSS necesario es el siguiente:

<style>
  .submenu{
    display:none;
  }
  .open .submenu{
    display:block;
  }
</style>   

Esto funciona de la siguiente manera: al principio los menus deplegables aparecen ocultos. Al hacer click en el enlace con las clase trigger añadiremos la clase open al li que contiente tanto al enlace como al menu desplegable.

Javascript

Antes de explicar el funcionamiento del script, aquí podeis ver su código:

<script type="text/javascript">
  (function($){
    $.fn.submenu = function () {
      var $this = $(this);
      function closeSubmenus() {
        $this.find('li').removeClass('open');
      }

      $('html').bind("click", closeSubmenus);

      return $this.each(function() {
        $this.delegate('.trigger', 'click', function (e) {
          var li = $(this).parent('li'),
              isActive = li.hasClass('open');
          closeSubmenus();
          !isActive && li.toggleClass('open');
          return false;
        });
      });
    }
  })(jQuery);
</script>

Esta función de plugin se invocará sobre el elemento de menu, es decir, sobre el ul principal:

<script type="text/javascript">
  $('#menu').submenu();
</script>

El primer paso es la tipica función anónima y autoejecutable que recibe un parámetro que se llamará $y que se invocará con el objeto jQuery.

De esta forma todo queda recogido en su propio contexto y podemos utilizar tranquilamente $ en caso de tener otras librerías cargadas en la página:

(function($){

})(jQuery);

A continuación creamos nuestra funcion de plugin y lo guardamos en el espacio de nombres de jQuery dedicado a ello:

$.fn.submenu = function(){

}

Esta función devuelve el elemento sobre el que invocamos el plugin para permitir llamadas encadenadas:

$.fn.submenu = function () {
  var $this = $(this);

  return $this.each(function() {
    // código del plugin
  });
}

Notad que he guardado una referencia a this encapsulada con jQuery. Esto tiene doble objetivo. El primero es evitar repetir llamadas a $(this) cacheando el resultado y el segundo es tener una referencia que podremos usar en funciones auxiliares como mostraré a continuación. this hace referencia al elemento #menu sobre el que hemos invocado el plugin.

Antes de añadir los comportamientos a los enlaces que desplegarán el submenú voy a crear una función auxiliar que utilizaré para cerrar el menú deplegable abierto, es decir, aquel que tiene la clase open y lo asigno al evento click de la página. Con esto conseguiré que al hacer click fuera del menú deplegable se cierre:

function closeSubmenus() {
  $this.find('li').removeClass('open');
}

$('html').bind("click", closeSubmenus);

Por último necesitamos que al pinchar en los enlaces con clase trigger se cierre el menú deplegable abierto (si lo hubiese) y que se abra el nuevo. Así que delegaremos sobre el menú para que cada click en el elemento con la clase trigger elimine la clase open de cualquier elemento li y lo añada al li padre del mismo:

$this.delegate('.trigger', 'click', function (e) {
  var li = $(this).parent('li'),
      isActive = li.hasClass('open');
  closeSubmenus();
  !isActive && li.toggleClass('open');
  return false;
});

 Y con esto queda el plugin terminado. Haz click aquí para verlo en acción