La clase WordPress Nav Walker: un var_dump() guiado | Programar Plus

La siguiente es una publicación de invitado de Scott Fennell. Scott es un desarrollador de complementos y temas de WordPress en Anchorage, Alaska. Aquí, personaliza la salida HTML predeterminada del sistema de menús de WordPress a su gusto, sin dañar las cosas útiles que ya hace.

Hay muchas cosas sobre el sistema de menú de navegación de WordPress que me gustan, y un puñado de cosas que realmente no me gustan. Las cosas que no me gustan suceden y desencadenan mis manías favoritas: los nombres de clases que se hinchan y no son SMACCS. Para mí, vale la pena una inmersión profunda para corregir esta situación. Sin embargo, no quiero perder la integración con la interfaz de usuario de administración, y no quiero perder los nombres de clase dinámicos que nos brinda el núcleo, como current_page_item o current-menu-ancestor. Por lo tanto, no voy a reemplazar nada: voy a extender la clase de PHP que dibuja los menús de navegación: El Walker_Nav_Menu clase.

Lo haré creando un complemento que genere menús de navegación con el marcado y las clases que desee. En el camino, me detendré y oleré las rosas. var_dump() las variables de PHP que WordPress nos expone. Este complemento tendrá los siguientes componentes:

  1. Un archivo de complemento principal para registrar el complemento y llamar a otros archivos
  2. Un shortcode para mostrar el menú.
  3. Algunos CSS, JS y SVG para hacer cosas como mostrar/ocultar submenús
  4. Una clase de caminante personalizada, que ampliará el núcleo. Walker_Nav_Menu clase

De esos componentes, todos menos el último actuarán principalmente como marcadores de posición. Ofrecerán la cantidad mínima de código para lograr el producto mínimo viable, y no los exploraré en detalle. Me proporcionarán una base suficiente para crear una clase de caminante personalizada, que es el tema central de este artículo.

suposiciones

  • Hagamos esto en el tema veinticinco
  • Si hay otros complementos activos, asegúrese de que no causen errores de JS o PHP. Si tienes dudas, desactívalas
  • Estoy en WordPress 4.3.1. en el momento de escribir

El complemento

Voy a citar bloques del complemento terminado a medida que avanzamos. Puede obtenerlo de mi repositorio de GitHub si desea consultar el producto terminado, o incluso instalarlo en un sitio de prueba de WordPress.

La salida del complemento terminado.

El código corto

El complemento funciona registrando un código abreviado, [csst_nav]. El shortcode toma un argumento, which_menu, donde puede elegir qué menú de navegación mostrar proporcionando el slug, ID o título de un menú de navegación. Aquí hay algunos ejemplos, donde tengo un menú llamado “Enlaces legales”, con una barra de legal-links y una identificación de 5:

  • [csst_nav]
  • [csst_nav which_menu='legal-links']
  • [csst_nav which_menu='Legal Links']
  • [csst_nav which_menu='5']

Usando el shortcode en el editor

El shortcode es un envoltorio para el wp_nav_menu() función, que requiere una tonelada de argumentos.

Aquí es donde me alejo de los valores predeterminados y hago lo que prefiero en su lugar:

  • menu: Quiero poder especificar qué menú tomar.
  • container: quiero menos marcado, por lo que no se necesita ningún elemento contenedor.
  • menu_class: Me encantan los nombres de las clases. Le daré a esto algunas clases con espacios de nombres para mi complemento y para el menú que estoy tomando.
  • echo: No gracias. Devolveré el menú en lugar de repetirlo.
  • items_wrap: Envolveré los artículos en una <nav> en lugar de la lista desordenada predeterminada.
  • before: Abriré cada elemento del menú como un <span>, y también deshágase de los códigos duros del núcleo <li>.
  • after: Cerraré cada elemento del menú con un cierre </span>, y también deshágase de los códigos duros del núcleo </li>.
  • before_submenu: Abriré cada submenú como un <span> en vez de una <ul>.
  • after_submenu: Cerraré cada submenú con un cierre <span>, en lugar de un cierre </ul>.
  • walker: Es por eso que estás leyendo este artículo. Le diré a WordPress que use nuestra clase de caminante personalizada.

Algunos de esos argumentos, como before_submenu y after_submenu, en realidad no se envía con wp_nav_menu(). Sin embargo, está bien, ya que todavía se pasan a la clase de caminante donde puedo usarlos como quiera.

Así es como se ve todo en el código:

<?php

  /**
   * The main template tag for this class.  Get a custom menu via our walker.
   * 
   * @return string A WordPress custom menu, passed through our walker class.
   */
  public function get() {
    
    // The CSS class for our shortcode.
    $class = strtolower( __CLASS__ );

    // Get a menu from the db.
    $which_menu = $this -> which_menu;

    /**
     * Args for a call to wp_nav_menu().
     * 
     * Some of these args don't get used by wp_nav_menu() per se,
     * but we're able to pass them through to our walker class, which does use them.
     */ 
    $menu_args = array(

      // Instead of wrapping each menu item as list item, let's do a span.
      'after' => '',

      // The closing markup after a submenu.
      'after_submenu' => '',

      // Instead of wrapping each menu item as list item, let's do a span.
      'before' => '',

      // The opening markup before a submenu.
      'before_submenu' => '',

      // Nope, we don't need extra markup wrapping our menu.
      'container' => FALSE,

      // Nope, let's return instead of echo.
      'echo' => FALSE,

      // Let's use a <nav> instead of a nested list.
      'items_wrap' => '<nav role="navigation" class="%2$s">%3$s</nav>',

      // Which menu to grab?  Takes ID, name, or slug.
      'menu' => $which_menu,

      // CSS classes for our menu.
      'menu_class' => "$class $class-$which_menu",

      // Our custom walker.
      'walker' => new CSST_Nav_Walker(),

    );

    // The main content of the shortcode is in fact a call to wp_nav_menu().
    $out = wp_nav_menu( $menu_args );

    return $out;

  }

?>

Muy bien, suficiente con el preámbulo. Es hora de sumergirse en la clase de andador personalizado. ¡Me encantan los detalles insoportables!

La clase de andador personalizado

Hay algo así como una jerarquía aquí:

  1. Core define una clase extremadamente genérica: Walker. Su propósito es iterar a través de estructuras complejas como matrices multidimensionales y hacer cosas en cada miembro de esa estructura.
  2. Core luego define una extensión más específica de Walker, hecho específicamente para navegar a través de los menús de navegación: Walker_Nav_Menu.
  3. Finalmente, defino mi propia extensión de Walker_Nav_Menu, llamándolo CSST_Nav_Walker.

Mi clase de caminante personalizada extenderá los siguientes métodos del núcleo Walker_Nav_Menu:

  • start_el(), que agrega el marcado de apertura para los elementos del menú y los propios elementos del menú.
  • end_el(), que agrega los elementos del menú de marcado de cierre.
  • start_lvl(), que agrega el marcado de apertura para los submenús.
  • end_lvl(), que agrega el marcado de cierre para los submenús.

Esos son algunos nombres súper genéricos, ¿eh? Ese es el punto: Estamos heredando de Walker, que está destinado a poder iterar a través de cualquier tipo de estructura, por cualquier motivo. En ese contexto, la especificidad es el enemigo. ¡Cortemos la nomenclatura abstracta y averigüemos qué hace cada método por nosotros!

start_el( &$salida, $elemento, $profundidad = 0, $argumentos = matriz(), $id = 0 )

Este método dibuja el html de apertura para un elemento de menú y el elemento de menú en sí. Lleva cinco parámetros:

  1. &$output, que es todo el HTML del menú, hasta el elemento de menú “este”. Cuando digo “este” elemento de menú, comprenda que este método se llama una vez para cada elemento de menú.
  2. $item, que es el objeto Publicación de WP para este elemento del menú (los elementos del menú son, de hecho, publicaciones del nav_menu_item tipo de publicación), además de algunos datos adicionales específicos de los menús de navegación.
  3. $depth, que realiza un seguimiento de cuántos niveles de profundidad nos encontramos en el menú, como en los submenús anidados.
  4. $args, que es principalmente una serie de argumentos para wp_nav_menu(). Incluye los argumentos que pasamos en nuestra devolución de llamada de shortcode, además de todos los valores predeterminados que omitimos.
  5. $id, que está documentado en la fuente principal como el ID del elemento de menú actual, aunque no estoy seguro de si todavía es compatible.

La mayoría de estos parámetros son un poco decepcionantes, pero algunos de ellos contienen mucha información útil. Permítame var_dump()!

&$salida

Tenga en cuenta que esta variable tiene el prefijo ampersand, lo que significa que se pasa por referencia. Eso significa que el método no tiene que devolver nada, porque cualquier cosa que le suceda a esta variable dentro del método también afectará a la variable fuera del método. Esta es también la razón por la cual el var_dump() se vuelve muy grande muy rápidamente:

var_dump( esc_html( $output ) );

nos consigue:

<?php

string(0) ""

string(274) "
  
    Front Page
  
"

string(1066) "
  
    Front Page
  
  
    [...] (truncated)

Esto termina siendo unos 35kb de var_dump() texto, por lo que lo he truncado en gran medida. Solo muestro partes de los primeros tres elementos del menú. Ese es el marcado para los elementos de menú anteriores, en cada elemento de menú, por lo que le agregamos el elemento de menú actual.

$artículo

Este parámetro nos da el objeto WP Post para el elemento de menú actual, lo que lo convierte en el argumento más interesante de este método.

wp_die( var_dump( $item ) )

Nos da:

<?php

object(WP_Post)#358 (40) {
  ["ID"]                    => int(68)
  ["post_author"]           => string(1) "1"
  ["post_date"]             => string(19) "2015-10-07 01:05:49"
  ["post_date_gmt"]         => string(19) "2015-10-07 01:05:49"
  ["post_content"]          => string(1) " "
  ["post_title"]            => string(0) ""
  ["post_excerpt"]          => string(0) ""
  ["post_status"]           => string(7) "publish"
  ["comment_status"]        => string(6) "closed"
  ["ping_status"]           => string(6) "closed"
  ["post_password"]         => string(0) ""
  ["post_name"]             => string(2) "68"
  ["to_ping"]               => string(0) ""
  ["pinged"]                => string(0) ""
  ["post_modified"]         => string(19) "2015-10-07 01:05:49"
  ["post_modified_gmt"]     => string(19) "2015-10-07 01:05:49"
  ["post_content_filtered"] => string(0) ""
  ["post_parent"]           => int(0)
  ["guid"]                  => string(33) "http://localhost/wp/csstnav/?p=68"
  ["menu_order"]            => int(1)
  ["post_type"]             => string(13) "nav_menu_item" 
  ["post_mime_type"]        => string(0) ""
  ["comment_count"]         => string(1) "0" 
  ["filter"]                => string(3) "raw"
  ["db_id"]                 => int(68)
  ["menu_item_parent"]      => string(1) "0"
  ["object_id"]             => string(2) "50"
  ["object"]                => string(4) "page"
  ["type"]                  => string(9) "post_type"
  ["type_label"]            => string(4) "Page"
  ["url"]                   => string(28) "http://localhost/wp/csstnav/"
  ["title"]                 => string(10) "Front Page"
  ["target"]                => string(0) ""
  ["attr_title"]            => string(0) ""
  ["description"]           => string(0) ""
  ["classes"]               => array(8) {
    [0]=> string(0) ""
    [1]=> string(9) "menu-item" 
    [2]=> string(24) "menu-item-type-post_type" 
    [3]=> string(21) "menu-item-object-page" 
    [4]=> string(17) "current-menu-item" 
    [5]=> string(9) "page_item"
    [6]=> string(12) "page-item-50"
    [7]=> string(17) "current_page_item"
  }
  ["xfn"]                   => string(0) "" 
  ["current"]               => bool(true)
  ["current_item_ancestor"] => bool(false)
  ["current_item_parent"]   => bool(false)
}

Bastante ordenado, ¿verdad? Podríamos acceder a ese objeto de publicación y obtener un montón de cosas interesantes como el extracto, la fecha, las taxonomías. ¡Diablos, tal vez podríamos diseñar una forma de hacer imágenes destacadas para los elementos del menú de navegación! Además de estos valores que normalmente vemos para las publicaciones, hay un par de elementos nuevos, como classes. Ahí es donde se puede encontrar la impresionante variedad de clases dinámicas de CSS: cosas como current-menu-item. También es de destacar object, que nos brinda detalles sobre a qué está vinculado este elemento del menú: tal vez una página o un archivo de términos.

$profundidad

Profundidad mantiene una cuenta corriente de muchos submenús “profundos” que somos. No tengo ningún uso para esto, pero estoy dispuesto a detenerme y admirar lo que Core hace con él: lo usan para anteponer caracteres de tabulación (como, literalmente, “t”) para que el código fuente sea más legible. Al menos asumo que es por eso. Núcleo bien jugado, bien jugado.

En vez de var_dump() $depth, es más instructivo agregarlo a &amp;$output para cada artículo. Puede ver cómo está rastreando el nivel de cada elemento:

Una demostración de $profundidad, desde el start_el() método.

$argumentos

$args debería parecer familiar: es principalmente la matriz de valores que pasé a wp_nav_menu() en nuestro código abreviado. Además, los valores predeterminados para cualquier argumento que omití.

var_dump( esc_html( $args ) );

nos consigue:

<?php

object(stdClass)#341 (16) {
	["menu"]            => string(13) "a-nested-menu"
	["container"]       => bool(false)
	["container_class"] => string(0) "" 
	["container_id"]    => string(0) "" 
	["menu_class"]      => string(31) "csst_nav csst_nav-a-nested-menu" 
	["menu_id"]         => string(0) "" 
	["echo"]            => bool(false) 
	["fallback_cb"]     => string(12) "wp_page_menu" 
	["before"]          => string(0) "" 
	["after"]           => string(0) "" 
	["link_before"]     => string(0) "" 
	["link_after"]      => string(0) "" 
	["items_wrap"]      => string(46) "%3$s"
	["depth"]           => int(0) 
	["walker"]          => object( CSST_Nav_Walker )#339 (5) {
		["icon"]     => string(96) "
			<svg class="csst_nav_svg-icon">
				<use xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href="https://css-tricks.com/the-wordpress-nav-walker-class-a-guided-var_dump/#csst_nav_svg-icon"></use>
			</svg>
		" 
		["tree_type"]=> array(3) {
			[0] => string(9) "post_type" 
			[1] => string(8) "taxonomy"
			[2] => string(6) "custom"
		}
		["db_fields"] => array(2) {
			["parent"] => string(16) "menu_item_parent" 
			["id"]     => string(5) "db_id"
		} 
		["max_pages"]    => int(1) 
		["has_children"] => bool(false)
	}
	["theme_location"] => string(0) ""
}

Es de destacar el walker argumento Puede ver que nombra nuestra clase de caminante e incluso captura el ícono SVG que guardamos como miembro de la clase. Los otros elementos bajo el walker arg no se usan o no son interesantes para nuestro propósito de personalizar un menú de navegación.

identificación de $

$id parece ser una gran decepción. Siempre es 0. Ni siquiera lo volcaré por ti.

Usos prácticos para el start_el() argumentos

Comencemos con lo que hace el núcleo en Walker_Nav_Menu -> start_el(). Como mencioné anteriormente, utilizan $depth para anteponer tabulaciones, aparentemente en busca de un código fuente más legible. ¡Qué artesanía! Además, es mejor que creas que toman esas clases de CSS de $item.

En mi versión personalizada, tengo dos valores agregados. Primero, tengo la oportunidad de crear el elemento del menú de acuerdo con mis propias preferencias de codificación. Resulta que odio los operadores ternarios, por ejemplo. En segundo lugar, tengo la oportunidad de asignar un espacio de nombres a todas las clases de CSS que WordPress genera para el elemento del menú. current-menu-item se convertiría csst_nav-current_menu_item. Hago esto pasando las clases css a un método personalizado que cambia el nombre de las clases y las devuelve. Vuelven con el prefijo de nuestro proyecto y un formato más consistente en cosas como guiones y guiones bajos.

Eso lo hace por start_el()! No tengo nada más que decir sobre el código HTML de apertura de un elemento de menú. Pero ahora que está abierto, será mejor que lo cerremos.

end_el( &$salida, $elemento, $profundidad = 0, $argumentos = matriz() )

El end_el() es “>un método muy corto: todo lo que hace es agregar el html de cierre para un elemento de menú. Lleva los mismos argumentos que start_el(), excepto por $id, que omite. También, &amp;$output será más grande de lo que era cuando lo encontramos en start_el(), desde el actual $item se le ha anexado. Estos argumentos son var_dump()‘d en mi discusión de start_el(), así que no los repasaré de nuevo.

En cuanto al uso práctico, es interesante notar que core simplemente imprime un cierre li. En cambio, estoy llegando de nuevo a $args para cerrar el elemento con el marcado que especifiqué a través del after arg al crear nuestro shortcode.

start_lvl( &$salida, $profundidad = 0, $argumentos = matriz() )

El propósito de este tipo de nombre extraño es comenzar un nuevo “nivel” en la estructura en la que estamos excavando. Eso suena bastante abstracto, pero afortunadamente tenemos un ejemplo muy familiar a nuestro alcance: en un menú de navegación, ¡un nuevo nivel es simplemente un submenú!

Este método lleva tres parámetros, &$output, $depth, y $args, que son todos var_dump()d arriba. En cuanto al uso, core aprovecha esta oportunidad para abrir una nueva ul para el submenú, complete con el código fuente sangrado. Muy agradable. Sin embargo, muchas veces me he encontrado descontento con el tratamiento del submenú. Por ejemplo, quiero agregar un ícono de alternar para indicar que hay un submenú. Quiero que el submenú use mis clases de marcado y CSS. Y quiero que el submenú responda como mostrar/ocultar cuando se hace clic en el interruptor. Este es el momento perfecto para hacer estas personalizaciones.

Buenos tiempos: nuestro submenú está abierto y los elementos del submenú se agregarán a través de start_el() y end_el(), sobre. Si hay submenús dentro de este elemento de submenú, no hay problema. Estos se adjuntarán a través de start_lvl() también. Una vez que todo esté hecho, tendremos que cerrar nuestro submenú.

end_lvl( &$salida, $profundidad = 0, $argumentos = matriz() )

Este método es muy similar a end_el(), solo que en lugar de cerrar un elemento de menú, cierra un submenú. Para el núcleo, eso es un cierre. ul. Para mi es un cierre span.

Otros elementos

Mi andador personalizado tiene otros elementos: un constructor y un par de atributos. Utilizo el constructor para llamar a mi clase de icono svg y tomar un icono de alternar para los submenús. Guardo el ícono como un atributo en la clase, por lo que mis otros métodos pueden usarlo fácilmente.

del núcleo Walker_Nav_Menu La clase también tiene otros elementos:

  • Un atributo misterioso llamado $tree_type, que incluso el núcleo no usa. La fuente lo documenta como “Lo que maneja la clase”, y un var_dump() Nos da:
    <?php
    
    	array(3) {
    		[0]=> string(9) "post_type"
    		[1]=> string(8) "taxonomy"
    		[2]=> string(6) "custom"
    	}
    	
    ?>

    Que, meh, lo que sea.

  • Un atributo llamado $db_fields, que es un poco opaco. A var_dump() Nos da:
    <?php
    
    	array(2) {
        
    		["parent"] => string(16) "menu_item_parent"
    		["id"]     => string(5) "db_id"
        
    	}
    
    ?>

    A lo cual, me rindo. Si puede descubrir cómo se usan y cómo podemos aprovecharlos para algo interesante, ¡déjelo en los comentarios!

Recursos y próximos pasos

Walker y sus herederos no están tan discutidos o documentados como otras partes de WordPress, que es una de las cosas que me inspiraron a escribir este artículo. Sin embargo, hay algunos trabajos previos disponibles. Me interesé por primera vez en las inmersiones profundas de los andadores cuando vi este puerto de un menú de navegación de BootStrap. Y, como era de esperar, el códice también da un par de ejemplos.

El eje principal que he estado puliendo en este artículo ha sido obtener el control de los nombres de mis clases y marcar los elementos de navegación y los submenús, pero hay muchas otras posibilidades. Tal vez podríamos llegar a $item y tome la imagen destacada o alguna publicación meta, si $item pasa a ser un enlace a una publicación. Si resulta que está vinculado a un archivo de términos, tal vez querríamos tomar algo del próximo sistema term_meta. Incluso podría hacer algo totalmente diferente, como elementos de menú de salida con el marcado y las clases que espera su widget jQueryUI favorito o control deslizante de imágenes. Pruébalo y feliz var_dump()¡En g!

(Visited 5 times, 1 visits today)