Wp_nav_menu'yu özel yürüteç ile böl


16

En fazla 5 öğe gösteren bir menü oluşturmaya çalışıyorum. Daha fazla öğe varsa, <ul>bir açılır liste oluşturmak için bunları başka bir Öğeye sarmalıdır.

5 veya daha az ürün:

Yıkılmak

6 Ürün veya daha fazla

Yıkılmak

Ben bu tür işlevsellik kolayca menü öğeleri sayar ve daha sonra 5 ayrı bir kalan varsa sarar bir walker ile oluşturulabilir biliyorum <ul>. Ama bu yürüteç nasıl yaratılacağını bilmiyorum.

Şu anda menümü gösteren kod şöyledir:

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

Menü kullanıcı tarafından tanımlanmadıysa ve bunun yerine geri dönüş işlevini kullanıyorsa, yürüteçin bir etkisi olmadığını fark ettim. Her iki durumda da çalışmasına ihtiyacım var.


1
Özel menü yürüteç bir sınıftır Walker_Nav_Menuve kodeksinde bir örnek vardır . "Walker nasıl yaratılacağını bilmiyorum" ile ne demek istiyorsun?
cybmeta

Btw, +1 çünkü fikir aslında oldukça harika. Buna nasıl rastladın? Kaynak mesajınız veya başka bir şey var mı? Eğer öyleyse, bunu okumaktan mutluluk duyarım. Şimdiden teşekkürler.
kaiser

@kaiser sadece kötü bir tasarım fikri :) kaynak yok, bu yüzden soruyorum.
Kartopu

@cybmeta Yürüteç oluşturmayı ve kodeksinde bir örnek olduğunu biliyorum, ama bu özel sorun için bir örnek yok. Bu yüzden bana bir çözüm sağlayan özel bir yürüteç nasıl oluşturulacağını bilmiyorum
Kartopu

Sen sormalısınız UX.SE bu fikri adamları ve kullanıcı için o orada sizsiniz, sorunları olmadığını doğrulamak. UX, kullanılabilirlik / deneyim ve düzenli olarak iyi düşünülmüş cevaplar ve problemler üzerinde oldukça iyi bir gerçeklik kontrolü getiren gerçekten harika bir sitedir. O zaman geri gelebilirsin ve hepimiz bu fikri birlikte düzeltiriz. (bu gerçekten harika olurdu!).
kaiser

Yanıtlar:


9

Özel bir Walker kullanarak, start_el()yöntem param'a erişebilir $depth: 0elemnt en iyisi olduğunda ve bu bilgiyi dahili bir sayacı korumak için kullanabiliriz.

Sayaç bir sınıra ulaştığında DOMDocument, tam HTML çıktısından yalnızca son eklenen öğeyi almak, bir alt menüye sarmak ve tekrar HTML'ye eklemek için kullanabiliriz.


Düzenle

Eleman sayısı tam olarak ihtiyacımız olan sayı + 1 olduğunda, örneğin 5 elemanın görünür olması ve menünün 6 olması gerektiğinde, menüyü bölmek mantıklı değildir, çünkü elemanlar her iki şekilde 6 olacaktır. Kod, bunu ele almak için düzenlendi.


İşte kod:

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

Kullanımı oldukça basit:

// 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>')
));

Mükemmel çalışıyor! Harika iş Giuseppe. Bununla ilgili en iyi şey, ilk 5 menü öğesinde bir alt menü varsa da çalışmasıdır. Ve eğer gerek yoksa alt menüye sadece tek bir menü noktasını sarmaz. Sadece küçük bir şey: Varsayılan olarak 6 öğeyi gösterir, $split_at = 5ancak $countdizin 0'da başlar.
Snowball

Bu küçük sorunu düzelttim, şimdi menü $split_atvarsayılan olarak argüman olarak geçirilen tam sayıyı gösteriyor , 5.
gmazzap

10

Bunu sadece CSS ile mümkün kılmanın bir yolu bile var. Bunun bazı sınırlamaları var, ama yine de ilginç bir yaklaşım olabileceğini düşündüm:

Sınırlamalar

  • Açılır pencerenin genişliğini sabit olarak kodlamanız gerekir
  • Tarayıcı Desteği. Temel olarak CSS3 seçicilerine ihtiyacınız var . Ama bunu test etmedim, ancak IE8 kadar her şey çalışması gerekir.
  • Bu daha çok bir kavram kanıtıdır. Yalnızca alt öğe yoksa çalışmak gibi çeşitli dezavantajlar vardır.

Yaklaşmak

Gerçekten "Miktar Sorguları" yaratıcı kullanım kullanmıyorum rağmen :nth-childve ~ben son okudum CSS Miktar Sorgular bu çözümün götürdü nelerdi.

Yaklaşım temel olarak şudur:

  1. 4.den sonra tüm öğeleri gizle
  2. ...Bir beforesözde öğe kullanarak noktalar ekleyin .
  3. Noktaları (veya gizli öğelerin herhangi birini) gezdirirken ekstra öğeleri, yani alt menüyü gösterin.

İşte varsayılan bir WordPress menü işaretlemesi için CSS kodu. Satır içi yorum yaptım.

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

Ayrıca eylemde göstermek için bir jsfiddle oluşturdum: http://jsfiddle.net/jg6pLfd1/

Bunun nasıl çalıştığı hakkında başka sorularınız varsa lütfen yorum bırakın, kodu daha fazla açıklığa kavuşturmaktan memnuniyet duyarız.


Yaklaşımınız için teşekkürler. Zaten CSS ile yapmayı düşündüm ama ben doğrudan php içinde yapmak daha temiz olduğunu düşünüyorum. Ek olarak bu çözüm 5. menü noktasını bir alt menüye yerleştirir, ayrıca sadece beş menü öğesi olup olmadığına da gerek yoktur.
Kartopu

Sadece 5+ öğe için etkinleştirilmesi muhtemelen düzeltilebilir. Her neyse, bunun mükemmel olmadığını ve bir PHP yaklaşımının daha temiz olabileceğini biliyorum. Ama yine de tamlık uğruna dahil etmeyi yeterince ilginç buldum. Başka bir seçenek her zaman güzel. :)
kraftner

2
Elbette. Btw. buna başka bir alt menü eklerseniz bu da kırılır
Kartopu

1
Elbette. Bu şimdiye kadar bir kavram kanıtı. Bir uyarı eklendi.
kraftner

8

wp_nav_menu_itemsFiltre kullanabilirsiniz . Menü çıktısını ve menü bilgi kutusu, kapsayıcı vb.

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

1
Birkaç küçük sorunu düzenledim, ancak bu yalnızca tüm menü öğelerinin alt öğesi yoksa çalışır. Çünkü Regex hiyerarşiyi tanımıyor. Test edin: İlk 4 menü öğesinden herhangi biri alt öğe içeriyorsa, menü oldukça yok edilir.
gmazzap

1
Bu doğru. Bu durumda DOMDocumentkullanılabilir. Bununla birlikte, bu soruda alt menüler yoktur, bu nedenle bu özel durum için cevap doğrudur. DOMDocument "evrensel" bir çözüm olurdu ama şu anda vaktim yok. Araştırmak;) LI öğeleri atlayarak, eğer bir UL çocuk atlayın, bu çözüm olurdu ama yazılı sürüm gerekiyor :)
mjakic

1
(a) OP'de alt menü olup olmadığını bilmiyorsunuz. Fare bittiğinde alt menüler görünür, bu nedenle ... (b) Evet, DOMDocument işe yarayabilir, ancak bu durumda iç denetimi yapmak için öğeleri yinelemeli olarak döngüye almanız gerekir ul. WordPress zaten menü yürüteç içindeki menü öğelerini döngüler. Zaten yavaş bir işlemdir se başına , ekleme ve bence ek döngü, özel bir yürüteç daha temiz ve verimli bir çözüm olacaktır countrary üzerine doğru çözüm değildir.
gmazzap

Teşekkürler çocuklar, ancak @gmazzap doğrudur, diğer menü noktalarının (hem ilk 4 hem de diğer olanlar) başka bir alt menü içermesi olasılığı vardır. Yani bu ruh işe yaramayacak.
Kartopu

Ana menü ve "gizli" menü olmak üzere iki menü de yerleştirebilirsiniz. Üç nokta "..." ile stilize edilmiş bir düğme ekleyin ve tıklayın veya fareyle üzerine gelin ikinci menüyü gösterin. Süper kolay olmalı.
mjakic

5

Çalışan bir işlev var, ancak en iyi çözüm olup olmadığından emin değilim.

Özel bir yürüteç kullandım:

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

  }
}

Gerçek menüyü gösteren işlev aşağıdaki gibidir:

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

Genel değişken $ menu_items bildirdim ve kapanış <li>ve <ul>-tags göstermek için kullandım. Muhtemelen bunu özel yürüteç içinde yapmak mümkündür, ama nerede ve nasıl bulamadım.

İki sorun: 1. Menüde sadece 5 Öğe varsa, son öğeyi de bir alternatif haline getirir, buna gerek yoktur.

  1. Sadece kullanıcı theme_location için bir menü tahsis ederse çalışır, wp_nav_menu geri dönüş işlevini gösteriyorsa yürüteç ateş etmez

İlk 4 öğeden herhangi birinin alt menüsü varsa ne olduğunu denediniz mi? İpucu: substr_count($output,'<li')olacak == 4... yanlış yerde
gmazzap
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.