Personalizar menús en WordPress con un Walker

Por default, WordPress agrega a los menús de navegación atributos que no siempre necesitamos ya sea porque son muy específicos o porque nuestro menú no es tan complejo. Por otro lado, a pesar de tantos atributos existen algunos que nos serían muy útiles pero que no los incluye el menú, como por ejemplo la posibilidad de agregar una clase a los elementos que tengan descendientes. Para adaptar el menú a nuestras necesidades, podemos aplicar filtros o utilizar un Walker. De las dos opciones, la segunda es la que tiene posibilidades de personalización.

Estructura básico del Menú con HTML5

Antes de comenzar quiero dar un breve repaso a la estructura básica para crear menús:

<nav>
    <ul>
        <li><a href="#">Enlace</a></li>
        <li><a href="#">Enlace</a></li>
        <li><a href="#">Enlace</a>
            <ul>
                <li><a href="#">Subenlace</a></li>
            </ul>
        </li>
    </ul>
</nav>

En esta estructura podemos observar ciertos patrones, por ejemplo los elementos siempre estarán repesentados por una etiqueta li, por esta razón las clases menu-item no tiene mucho sentido, otro ejemplo son los submenús pues para acceder a ellos podemos escribir ul ul.

Creación del WalKer

El Walker es una extensión de la clase Walker_Nav_Menu, así que crearemos un archivo dentro de un folder que podría ser functions que se llame mi_menu_walker.php y escribiremos el siguente código.

class Mi_menu_walker extends Walker_Nav_Menu
{
    public function start_el( &$output, $item, $depth, $args )
    {
        /*
         * Abrimos el elemento, por el momento si clases
         */ 
        
        $output .= '<li>';

        /*
         * Atributos del enlace
         */ 
        // Evitamos titulos redundantes
        ! empty ( $item->attr_title )
            and $item->attr_title !== $item->title
            and $attributes .= ' title="' . esc_attr( $item->attr_title ) .'"';

        ! empty ( $item->url )
            and $attributes .= ' href="' . esc_attr( $item->url ) .'"';

        //Mostramos el atributo rel
        ! empty( $item->xfn )  
            and $attributes .= ' rel="'    . esc_attr( $item->xfn        ) .'"';

        $attributes  = trim( $attributes );
        $title       = apply_filters( 'the_title', $item->title, $item->ID );

        $anchor = "{$args->before}<a {$attributes}>{$args->link_before}{$title}</a>"
                    . $args->link_after . $args->after;

        //Agregamos un filtro par si un plugin necesita acceso
        $output .= apply_filters(
            'walker_nav_menu_start_el'
            ,   $anchor
            ,   $item
            ,   $depth
            ,   $args
        );
    }

    function end_el( &$output )
    {
        $output .= '</li>';
    }
}

Con esto tenemos una estructura limpia a la que podemos agregar los atributos que necesitamos. Dentro de la función start_el tenemos acceso a diferentes variables, la primera $output debe incluirse como referencia1 porque se trata de el menú, así que debe incluirse el operador .= para agreagr información. La variable $item es un objeto que contiene la información del enlace, de forma similar a un post2, es de este objeto de donde tomaremos los datos para trabajar.

Lo que falta es incluir este archivo al tema: dentro de nuestro archivo functions.php lo incluimos con include functions/mi_menu_walker.php

Asignar el Walker a un Menú

Para ver los resultados necesitamos decirle a WordPress en que menú queremos que utilice nuestro Walker, lo hacemos en nuestro tema al ejecutar el menu con el siguiente código:

$args = array(
    [...]
    'walker' => new Mi_menu_walker;
);

wp_nav_menu($args);

Personalizar el Menú

Una vez implementado el Walker, vamos a ir agregando los atributos que considero serían los más importantes. Trabajaremos exclusivamente dentro de la funcion start_el.

Agregar Clase de elemento actual

La clase actual o en inglés current nos indica en que elemento nos encontramos, para agregarla debemos buscar en la variable $item. Considero que no es necesaria tanta especificidad en las clases, y con utilizar la clase current independientemente del nivel, podemos acceder a todos ellos. Y como utilizaremos varias clases comenzaremos guardando estas dentro de un array.

$classes = array();
$classes[] = ($item->current OR $item->current_item_ancestor OR $item->current_item_parent)
        ? 'current' : '';

Clase para elemento con descendientes

Para agregar esta clase, Jan Fabry propone que primero tenemos que forzar a que WordPress incluya la variable has_children dentro del array $args, para ello agregamos el siguiente método a nuestro Walker

public function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )
{
    $id_field = $this->db_fields['id'];
    if ( is_object( $args[0] ) ) {
        $args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
    }
    return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
}

Y ahora dentro de nuestra función podemos agregar la clase parent, según el valor de la variable has_children.

$classes[] = ($arg->has_children) ? 'parent' : '';

Clase para identificar elementos

Poder identificar elementos individualmente, nos permite agregar estilos diferentes a cada uno ellos. Por default WordPress agrega un id de la siguiente forma a los elementos: id="item-35", siendo 35 el identificador en la base de datos, el cual varia en cada instalación, por lo que podemos tener un id cuando trabajamos de manera local y otro cuando publicamos el sitio o tema. Una solución podría ser que en lugar de agregar el id como identificador utilicemos el nombre o slug como una clases así tendríamos, por ejemplo, class="contacto". Para incluir dicha clase necesitamos el siguiente código.

$classes[] = sanitize_html_class(sanitize_title($item->title));

Agregar las clases al elemento li

En nuestro array $classes ya tenemos guardadas las clases que necesitamos, ahora las agregamos al elemento con el siguiente código.

$output     .= '<li class="' . trim(implode(' ', $classes)). '">';

Agregar Descripción

Aunque la descripción no es muy utilizada, creo que podrían crearse menús como el de [css-trick], donde además del enlace tenemos una breve descripción de lo que trata. Para agregar la descripción necesitamos el siguiente código.

 $description = ($item->description != '') 
                 ? "<span class=\\"description\\"><small>{$item->description}</small></span>"
                 : '';

No se si las etiquetas span y small son las más adecuadas, pero utilizo la etiqueta small no para hacer el texto más pequeño, sino para establecer jerarquías3.

Clase Mi_menu_walker

Puedes descargar la clase completa desde GitHub.

Referencias

Notas al pie

  1. Creo que en lugar de utilizar la variable output de esta manera, se debió haber utilizado una propiedad, por ejemplo $this->menu_output.

  2. De hecho los enlaces son tratados como entradas en la base de datos de WordPress

  3. Ver http://html5doctor.com/small-hr-element/