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:
6 articles ou plus
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.
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.
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="#">…</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>')
));
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:
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:
...
à l'aide d'un pseudo-élément before
.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.
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">…<ul>'
. $matches[0][5];
// $matches contain multidimensional array
// first (and only) item is found matches array
return implode('', $matches[0]) . '</ul></li>';
}
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.