Ich versuche, ein Menü zu erstellen, das maximal 5 Elemente anzeigt. Wenn weitere Elemente vorhanden sind, sollten diese in ein anderes <ul>
-Element eingeschlossen werden, um ein Dropdown-Menü zu erstellen.
5 Artikel oder weniger:
6 Artikel oder mehr
Ich weiß, dass diese Art von Funktionalität leicht mit einem Walker erstellt werden kann, der die Menüpunkte zählt und umschließt, wenn mehr als 5 in einem separaten <ul>
verbleiben. Aber ich weiß nicht, wie ich diesen Walker bauen soll.
Der Code, der mein Menü im Moment anzeigt, ist der folgende:
<?php wp_nav_menu( array( 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) ); ?>
Mir ist aufgefallen, dass der Benutzer keine Auswirkung hat, wenn das Menü nicht vom Benutzer definiert wurde und stattdessen die Fallback-Funktion verwendet wird. Ich brauche es in beiden Fällen zu arbeiten.
Wenn Sie einen benutzerdefinierten Walker verwenden, hat die Methode start_el()
Zugriff auf den Parameter $depth
: Wenn es sich um 0
handelt, ist das Element das oberste, und wir können diese Informationen verwenden, um einen internen Zähler zu verwalten.
Wenn der Zähler ein Limit erreicht, können wir DOMDocument
verwenden, um von der vollständigen HTML-Ausgabe nur das zuletzt hinzugefügte Element abzurufen, es in ein Untermenü zu packen und es erneut zu HTML hinzuzufügen.
Wenn die Anzahl der Elemente genau der Anzahl entspricht, die wir benötigt haben, + 1, z. wir brauchten 5 Elemente, und menu hat 6, es macht keinen Sinn, das Menü zu teilen, da die Elemente in beide Richtungen 6 sind. Der Code wurde bearbeitet, um dies zu beheben.
Hier ist der 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;
}
}
Die Verwendung ist ziemlich einfach:
// 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>')
));
Es gibt sogar eine Möglichkeit, dies nur mit CSS zu ermöglichen. Dies hat einige Einschränkungen, aber ich dachte immer noch, dass es ein interessanter Ansatz sein könnte:
Obwohl ich "Mengenabfragen" nicht wirklich verwende, führte mich die kreative Verwendung von :nth-child
und ~
, die ich in den letzten Mengenabfragen für CSS gelesen habe, zu dieser Lösung.
Der Ansatz ist im Grunde:
...
Punkte mit einem before
Pseudoelement hinzu.Hier ist der CSS-Code für ein Standard-WordPress-Menü-Markup. Ich habe inline kommentiert.
/* 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;
}
Ich habe auch eine jsfiddle erstellt, um sie in Aktion zu zeigen: http://jsfiddle.net/jg6pLfd1/
Wenn Sie weitere Fragen haben, hinterlassen Sie bitte einen Kommentar, ich würde mich freuen, den Code weiter zu klären.
Sie können den wp_nav_menu_items
-Filter verwenden. Es akzeptiert Menüausgaben und Argumente, die Menüattribute enthalten, wie Menü-Slug, Container usw.
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>';
}
Ich habe eine funktionierende Funktion, bin mir aber nicht sicher, ob es die beste Lösung ist.
Ich habe einen Custom Walker benutzt:
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 );
}
}
Die Funktion, die das aktuelle Menü anzeigt, ist die folgende:
<?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>";
}
?>
Ich habe die globale Variable $ menu_items deklariert und damit die schließenden Tags <li>
und <ul>
- angezeigt. Es ist wahrscheinlich möglich, dies auch im Custom-Walker zu tun, aber ich habe nicht herausgefunden, wo und wie.
Zwei Probleme: 1. Wenn das Menü nur 5 Elemente enthält, wird auch das letzte Element in einen Gedanken eingeschlossen, der nicht erforderlich ist.