Dividir wp_nav_menu con un caminante personalizado

14

Estoy intentando crear un menú que muestra un máximo de 5 elementos. Si hay más elementos, debería incluirlos en otro elemento <ul> para crear un menú desplegable.

5 elementos o menos:

6elementosomás

Sé que este tipo de funcionalidad podría crearse fácilmente con un caminante que cuenta los elementos del menú y se ajusta si hay más de 5 el resto en un <ul> separado. Pero no sé cómo crear este caminante.

El código que muestra mi menú en este momento es el siguiente:

<?php wp_nav_menu( array( 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) ); ?>

Noté que si el menú no está definido por el usuario y usa la función de respaldo, el caminante no tiene ningún efecto. Lo necesito para trabajar en ambos casos.

    
pregunta Snowball 05.03.2015 - 09:23

4 respuestas

9

Usando un Walker personalizado, el método start_el() tiene acceso a $depth param: cuando es 0 , el elemnt es uno de los mejores, y podemos usar esta información para mantener un contador interno.

Cuando el contador alcanza un límite, podemos usar DOMDocument para obtener de la salida HTML completa solo el último elemento agregado, envolverlo en un submenú y agregarlo nuevamente a HTML.

Editar

Cuando el número de elementos es exactamente el número que requerimos + 1, por ejemplo. requerimos que 5 elementos sean visibles y el menú tiene 6, no tiene sentido dividir el menú, ya que los elementos serán 6 de cualquier manera. El código fue editado para abordar eso.

Aquí está el código:

class SplitMenuWalker extends Walker_Nav_Menu {

  private $split_at;
  private $button;
  private $count = 0;
  private $wrappedOutput;
  private $replaceTarget;
  private $wrapped = false;
  private $toSplit = false;

  public function __construct($split_at = 5, $button = '<a href="#">&hellip;</a>') {
      $this->split_at = $split_at;
      $this->button = $button;
  }

  public function walk($elements, $max_depth) {
      $args = array_slice(func_get_args(), 2);
      $output = parent::walk($elements, $max_depth, reset($args));
      return $this->toSplit ? $output.'</ul></li>' : $output;
  }

  public function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0 ) {
      $this->count += $depth === 0 ? 1 : 0;
      parent::start_el($output, $item, $depth, $args, $id);
      if (($this->count === $this->split_at) && ! $this->wrapped) {
          // split at number has been reached generate and store wrapped output
          $this->wrapped = true;
          $this->replaceTarget = $output;
          $this->wrappedOutput = $this->wrappedOutput($output);
      } elseif(($this->count === $this->split_at + 1) && ! $this->toSplit) {
          // split at number has been exceeded, replace regular with wrapped output
          $this->toSplit = true;
          $output = str_replace($this->replaceTarget, $this->wrappedOutput, $output);
      }
   }

   private function wrappedOutput($output) {
       $dom = new DOMDocument;
       $dom->loadHTML($output.'</li>');
       $lis = $dom->getElementsByTagName('li');
       $last = trim(substr($dom->saveHTML($lis->item($lis->length-1)), 0, -5));
       // remove last li
       $wrappedOutput = substr(trim($output), 0, -1 * strlen($last));
       $classes = array(
         'menu-item',
         'menu-item-type-custom',
         'menu-item-object-custom',
         'menu-item-has-children',
         'menu-item-split-wrapper'
       );
       // add wrap li element
       $wrappedOutput .= '<li class="'.implode(' ', $classes).'">';
       // add the "more" link
       $wrappedOutput .= $this->button;
       // add the last item wrapped in a submenu and return
       return $wrappedOutput . '<ul class="sub-menu">'. $last;
   }
}

El uso es bastante simple:

// by default make visible 5 elements
wp_nav_menu(array('menu' => 'my_menu', 'walker' => new SplitMenuWalker()));

// let's make visible 2 elements
wp_nav_menu(array('menu' => 'another_menu', 'walker' => new SplitMenuWalker(2)));

// customize the link to click/over to see wrapped items
wp_nav_menu(array(
  'menu' => 'another_menu',
  'walker' => new SplitMenuWalker(5, '<a href="#">more...</a>')
));
    
respondido por el gmazzap 06.03.2015 - 23:10
10

Incluso hay una manera de hacer esto posible con CSS solo. Esto tiene algunas limitaciones, pero aun así pensé que podría ser un enfoque interesante:

Limitaciones

  • Necesitas codificar el ancho del desplegable
  • Soporte de navegador. Básicamente necesita selectores CSS3 . Pero todo desde IE8 hasta arriba debería funcionar, aunque no he probado esto.
  • Esto es más una prueba de concepto. Hay varios inconvenientes, como solo trabajar si no hay subelementos.

Enfoque

Aunque realmente no estoy usando "consultas de cantidad", el uso de la creatividad de :nth-child y ~ lo he leído en el reciente Las consultas de cantidad para CSS fueron las que me llevaron a esta solución.

El enfoque es básicamente este:

  1. Ocultar todos los elementos después del 4º
  2. Agregue ... dots usando un pseudo-elemento before .
  3. Cuando se desplazan los puntos (o cualquiera de los elementos ocultos) se muestran los elementos adicionales, también conocido como el submenú.

Aquí está el código CSS para una marca de menú de WordPress predeterminada. He comentado en línea.

/* Optional: Center the navigation */
.main-navigation {
    text-align: center;
}

.menu-main-menu-container {
    display: inline-block;
}

/* Float menu items */
.nav-menu li {
    float:left;
    list-style-type: none;
}

/* Pull the 5th menu item to the left a bit so that there isn't too
   much space between item 4 and ... */
.nav-menu li:nth-child(4) {
    margin-right: -60px;
}

/* Create a pseudo element for ... and force line break afterwards
   (Hint: Use a symbol font to improve styling) */
.nav-menu li:nth-child(5):before {
    content: "...\A";
    white-space: pre;
}

/* Give the first 4 items some padding and push them in front of the submenu */
.nav-menu li:nth-child(-n+4) {
    padding-right: 15px;
    position: relative;
    z-index: 1;
}

/* Float dropdown-items to the right. Hardcode width of dropdown. */
.nav-menu li:nth-child(n+5) {
    float:right;
    clear: right;
    width: 150px;
}

/* Float Links in dropdown to the right and hide by default */
.nav-menu li:nth-child(n+5) a{
    display: none;      
    float: right;
    clear: right;
}   

/* When hovering the menu, show all menu items from the 5th on */
.nav-menu:hover li:nth-child(n+5) a,
.nav-menu:hover li:nth-child(n+5) ~ li a{
    display: inherit;
}

/* When hovering one of the first 4 items, hide all items after it 
   so we do not activate the dropdown on the first 4 items */
.nav-menu li:nth-child(-n+4):hover ~ li:nth-child(n+5) a{
    display: none;
}

También he creado un jsfiddle para mostrarlo en acción: enlace

Si tiene más preguntas sobre cómo funciona esto, deje un comentario. Me encantaría aclarar el código.

    
respondido por el kraftner 05.03.2015 - 15:14
8

Puedes usar wp_nav_menu_items filter. Acepta salida de menú y argumentos que contienen atributos de menú, como barra de menú, contenedor, etc.

add_filter('wp_nav_menu_items', 'wpse_180221_nav_menu_items', 20, 2);

function wpse_180221_nav_menu_items($items, $args) {
    if ($args->menu != 'my-menu-slug') {
        return $items;
    }

    // extract all <li></li> elements from menu output
    preg_match_all('/<li[^>]*>.*?<\/li>/iU', $items, $matches);

    // if menu has less the 5 items, just do nothing
    if (! isset($matches[0][5])) {
        return $items;
    }

    // add <ul> after 5th item (can be any number - can use e.g. site-wide variable)
    $matches[0][5] = '<li class="menu-item menu-item-type-custom">&hellip;<ul>'
          . $matches[0][5];

    // $matches contain multidimensional array
    // first (and only) item is found matches array
    return implode('', $matches[0]) . '</ul></li>';
}
    
respondido por el mjakic 05.03.2015 - 14:25
5

Obtuvo una función de trabajo, pero no estoy seguro de si es la mejor solución.

Usé un andador personalizado:

class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_el(  &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    global $wp_query;
    $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

    $classes = empty( $item->classes ) ? array() : (array) $item->classes;
    $classes[] = 'menu-item-' . $item->ID;

    $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
    $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

    $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
    $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';

    /**
     * This counts the $menu_items and wraps if there are more then 5 items the
     * remaining items into an extra <ul>
     */
    global $menu_items;
    $menu_items = substr_count($output,'<li');
    if ($menu_items == 4) {
      $output .= '<li class="tooltip"><span>...</span><ul class="tooltip-menu">';
    }

    $output .= $indent . '<li' . $id . $class_names .'>';

    $atts = array();
    $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
    $atts['target'] = ! empty( $item->target )     ? $item->target     : '';
    $atts['rel']    = ! empty( $item->xfn )        ? $item->xfn        : '';
    $atts['href']   = ! empty( $item->url )        ? $item->url        : '';

    $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

    $attributes = '';
    foreach ( $atts as $attr => $value ) {
      if ( ! empty( $value ) ) {
        $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
        $attributes .= ' ' . $attr . '="' . $value . '"';
      }
    }

    $item_output = $args->before;
    $item_output .= '<a'. $attributes .'>';
    $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
    $item_output .= '</a>';
    $item_output .= $args->after;

    $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

  }
}

La función que muestra el menú real es la siguiente:

        <?php
        wp_nav_menu( array( 'container' => false, 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) );
        global $menu_items;
        // This adds the closing </li> and </ul> if there are more then 4 items in the menu
        if ($menu_items > 4) {
            echo "</li></ul>";
        }
        ?>

Declaré la variable global $ menu_items y la utilicé para mostrar las etiquetas <li> y <ul> de cierre. Probablemente sea posible hacerlo también dentro del andador personalizado, pero no encontré dónde y cómo.

Dos problemas: 1. Si solo hay 5 elementos en el menú, envuelve el último elemento y no hay necesidad de hacerlo.

  1. Simplemente funciona si el usuario realmente ha asignado un menú a la ubicación de tema, el caminante no dispara si wp_nav_menu está mostrando la función de respaldo
respondido por el Snowball 06.03.2015 - 11:19

Lea otras preguntas en las etiquetas