it-swarm.com.de

Teilen Sie wp_nav_menu mit benutzerdefiniertem Walker auf

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:

Dropdown

6 Artikel oder mehr

Dropdown

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.

15
Snowball

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.


Bearbeiten

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="#">&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;
   }
}

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>')
));
9
gmazzap

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:

Einschränkungen

  • Sie müssen die Breite des Dropdowns fest codieren
  • Browser-Unterstützung. Sie benötigen grundsätzlich CSS3-Selektoren . Aber alles ab IE8 sollte funktionieren, obwohl ich das nicht getestet habe.
  • Dies ist eher ein Proof-of-Concept. Es gibt einige Nachteile, wie nur zu arbeiten, wenn es keine Unterelemente gibt.

Ansatz

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:

  1. Verstecke alle Gegenstände nach dem 4.
  2. Fügen Sie ... Punkte mit einem before Pseudoelement hinzu.
  3. Wenn Sie mit dem Mauszeiger über die Punkte (oder eines der ausgeblendeten Elemente) fahren, werden die zusätzlichen Elemente angezeigt, die auch als Untermenü bezeichnet werden.

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.

10
kraftner

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">&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

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.

  1. Es funktioniert nur, wenn der Benutzer dem theme_location tatsächlich ein Menü zugewiesen hat. Der Walker wird nicht ausgelöst, wenn im wp_nav_menu die Fallback-Funktion angezeigt wird
5
Snowball