web-dev-qa-db-fra.com

Séparez wp_nav_menu avec un programme personnalisé

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

5 articles ou moins:

Dropdown

6 articles ou plus

Dropdown

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 éléments restants dans un <ul> séparé. Mais je ne sais pas comment créer ce marcheur.

Le code qui montre 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.

15
Snowball

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 maintenir un compteur interne.

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


Modifier

Lorsque le nombre d'éléments est exactement le nombre requis, nous avons 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>')
));
9
gmazzap

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

Limites

  • Vous devez coder en dur la largeur du menu déroulant
  • Support par navigateur. Vous avez essentiellement besoin de sélecteurs CSS3 . Mais tout à partir d'IE8 devrait fonctionner, bien que je ne l'aie pas testé.
  • C'est plus 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 "Quantity Queries", les utilisations créatives de :nth-child et ~ que j’ai lues récemment Requêtes quantitatives pour CSS sont ce qui m’a conduit à cette solution.

L'approche est fondamentalement la suivante:

  1. Masquer tous les articles après le 4
  2. Ajoutez des points ... à l'aide d'un pseudo-élément before.
  3. Lorsque vous survolez les points (ou l’un des éléments cachés), affichez les éléments supplémentaires, c’est-à-dire 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: http://jsfiddle.net/jg6pLfd1/

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

10
kraftner

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>';
}
8
mjakic

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 balises <li> et <ul>- de clôture. Il est probablement possible de faire cela aussi à l'intérieur du 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 simplement 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.
5
Snowball