Divisez wp_nav_menu avec un lecteur personnalisé

14

J'essaie de créer un menu contenant 5 éléments maximum. S'il y a plus d'éléments, vous devez les envelopper dans un autre élément <ul> afin de créer une liste déroulante.

5 éléments ou moins:

6élémentsouplus

Je sais que ce type de fonctionnalité pourrait facilement être créé avec un lecteur qui compte les éléments de menu et s’emballe s’il ya plus de 5% des éléments restants dans un <ul> séparé. Mais je ne sais pas comment créer ce marcheur.

Le code qui affiche mon menu pour le moment est le suivant:

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

J'ai remarqué que si le menu n'est pas défini par l'utilisateur et qu'il utilise la fonction de repli, le déambulateur n'a aucun effet. J'ai besoin que cela fonctionne dans les deux cas.

    
posée Snowball 05.03.2015 - 09:23

4 réponses

9

En utilisant un Walker personnalisé, la méthode start_el() a accès à $depth param: quand il s'agit de 0 , elemnt est un top, et nous pouvons utiliser cette information pour gérer un compteur interne.

Lorsque le compteur atteint une limite, nous pouvons utiliser DOMDocument pour obtenir uniquement le dernier élément ajouté à la sortie HTML, l'envelopper dans un sous-menu et l'ajouter à nouveau au code HTML.

Modifier

Lorsque le nombre d’éléments est exactement le nombre requis, vous avez besoin de + 1, par exemple. nous avions besoin que 5 éléments soient visibles et que le menu en ait 6, cela ne sert à rien de diviser le menu, car les éléments seront 6 de toute façon. Le code a été modifié pour résoudre ce problème.

Voici le code:

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;
   }
}

L’utilisation est assez 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>')
));
    
réponse donnée gmazzap 06.03.2015 - 23:10
10

Il existe même un moyen de rendre cela possible avec CSS seul. Cela a quelques limites, mais je pensais toujours que cela pourrait être une approche intéressante:

Limitations

  • Vous devez coder en dur la largeur du menu déroulant
  • Prise en charge du navigateur. Vous avez en principe besoin de sélecteurs CSS3 . Mais tout depuis IE8 devrait fonctionner, même si je n’ai pas testé cela.
  • Il s’agit plus d’une preuve de concept. Il y a plusieurs inconvénients, comme le fait de ne travailler que s'il n'y a pas de sous-éléments.

Approche

Bien que je n'utilise pas vraiment les "requêtes de quantité", l'utilisation créative de :nth-child et ~ que j'ai lue récemment "a href=" http://alistapart.com/article/quantity-queries -for-css "> Les requêtes de quantité pour CSS m'ont amené à cette solution.

L’approche est fondamentalement la suivante:

  1. Masquer tous les éléments après le 4ème
  2. Ajoutez ... points en utilisant un pseudo-élément before .
  3. Lors du survol des points (ou de l'un des éléments cachés), affichez les éléments supplémentaires, à savoir le sous-menu.

Voici le code CSS pour un balisage de menu WordPress par défaut. J'ai commenté inline.

/* 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;
}

J'ai également créé un jsfiddle pour le montrer en action: enlace

Si vous avez d'autres questions sur la façon dont cela fonctionne, veuillez laisser un commentaire. Je serais ravi de clarifier le code.

    
réponse donnée kraftner 05.03.2015 - 15:14
8

Vous pouvez utiliser le filtre wp_nav_menu_items . Il accepte les sorties de menu et les arguments contenant les attributs de menu, tels que slug de menu, conteneur, 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>';
}
    
réponse donnée mjakic 05.03.2015 - 14:25
5

Vous avez une fonction qui fonctionne mais vous ne savez pas si c'est la meilleure solution.

J'ai utilisé un marcheur personnalisé:

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 fonction qui affiche le menu actuel est la suivante:

        <?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>";
        }
        ?>

J'ai déclaré la variable globale $ menu_items et je l'ai utilisée pour afficher les codes de clôture <li> et <ul> -tags. Il est probablement possible de le faire aussi dans le programme personnalisé, mais je n’ai pas trouvé où et comment.

Deux problèmes: 1. S'il n'y a que 5 éléments dans le menu, le dernier élément est également inclus dans un menu, bien que cela ne soit pas nécessaire.

  1. Cela fonctionne si l’utilisateur a effectivement alloué un menu à l’emplacement_thème, le lecteur ne se déclenche pas si wp_nav_menu affiche la fonction de secours
réponse donnée Snowball 06.03.2015 - 11:19

Lire d'autres questions sur les étiquettes