Membagi wp_nav_menu dengan custom walker

16

Saya mencoba membuat Menu yang menampilkan maksimal 5 item. Jika ada lebih banyak item itu harus membungkusnya menjadi <ul>Elemen lain untuk membuat dropdown.

5 Item atau kurang:

Dropdown

6 Item atau lebih

Dropdown

Saya tahu fungsi semacam ini dapat dengan mudah dibuat dengan alat bantu yang menghitung item menu dan membungkus jika ada lebih dari 5 remaing menjadi terpisah <ul>. Tapi saya tidak tahu cara membuat alat bantu jalan ini.

Kode yang menunjukkan menu saya saat ini adalah sebagai berikut:

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

Saya perhatikan bahwa jika menu tidak ditentukan oleh pengguna dan ia menggunakan fungsi mundur sebagai pengganti walker tidak memiliki efek. Saya membutuhkannya untuk bekerja dalam kedua kasus.

Semakin bertambah
sumber
1
Walker menu kustom adalah kelas yang diperluas Walker_Nav_Menudan ada contoh dalam codex . Apa maksud Anda dengan "Saya tidak tahu cara membuat Walker"?
cybmeta
Btw, +1 karena idenya sebenarnya cukup mengagumkan. Bagaimana Anda menemukan itu? Apakah Anda memiliki posting sumber atau sesuatu? Jika demikian, saya akan senang membacanya. Terima kasih sebelumnya.
kaiser
@kaiser hanya ide desain yang buruk :) tidak ada posting sumber, itu sebabnya saya bertanya.
Bola Salju
@cybmeta Saya tahu cara membuat alat bantu jalan dan juga ada contoh dalam kodeks, tetapi tidak ada contoh untuk masalah khusus ini. Jadi saya tidak tahu cara membuat custom walker yang memberi saya solusi
Snowball
Anda harus bertanya kepada orang-orang UX.SE tentang ide ini dan memverifikasi jika ada masalah dengan itu untuk pengguna. UX adalah situs yang benar-benar luar biasa yang menghadirkan realitas yang cukup baik tentang kegunaan / pengalaman dan jawaban serta masalah yang dipikirkan secara teratur. Anda juga bisa kembali dan kita semua memperbaiki gagasan itu bersama-sama. (itu akan sangat luar biasa!).
kaiser

Jawaban:

9

Menggunakan kustom Walker, start_el()metode ini memiliki akses ke $depthparam: ketika 0elemnt adalah yang teratas, dan kita bisa menggunakan info ini untuk mempertahankan penghitung internal.

Ketika penghitung mencapai batas, kita dapat menggunakan DOMDocumentuntuk mendapatkan dari hasil HTML penuh hanya elemen terakhir yang ditambahkan, bungkus dalam submenu dan tambahkan lagi ke HTML.


Edit

Ketika jumlah elemen persis jumlah yang kita butuhkan +1, misalnya kita membutuhkan 5 elemen terlihat dan menu memiliki 6, tidak masuk akal untuk membagi menu, karena elemen akan menjadi 6 cara baik. Kode diedit untuk mengatasi itu.


Berikut kodenya:

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

Penggunaannya cukup sederhana:

// 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>')
));
gmazzap
sumber
Sangat bagus! Giuseppe yang luar biasa. Hal yang hebat tentang itu adalah ia berfungsi juga jika ada submenu di elemen 5 menu pertama. Dan itu tidak membungkus hanya satu titik menu menjadi submenu jika tidak perlu. Hanya hal kecil: Secara default ini menunjukkan 6 elemen $split_at = 5tetapi $countindeks mulai dari 0.
Snowball
Terima kasih @Snowball Saya memperbaiki masalah kecil itu, sekarang menu menunjukkan angka pasti yang dilewati sebagai $split_atargumen, 5 secara default.
gmazzap
10

Bahkan ada cara untuk membuat ini mungkin dengan CSS saja. Ini memiliki beberapa keterbatasan, tetapi saya masih berpikir itu mungkin pendekatan yang menarik:

Keterbatasan

  • Anda perlu membuat hardcode lebar dropdown
  • Dukungan Browser. Anda pada dasarnya membutuhkan pemilih CSS3 . Tapi semuanya dari IE8 hingga bekerja, meskipun saya belum menguji ini.
  • Ini lebih merupakan bukti konsep. Ada beberapa kelemahan seperti hanya berfungsi jika tidak ada sub-item.

Pendekatan

Meskipun saya tidak benar-benar menggunakan "Kuantitas Kueri" penggunaan kreatif :nth-childdan ~saya telah membaca Kueri Kuantitas untuk CSS baru -baru ini yang mendorong saya ke solusi ini.

Pendekatannya pada dasarnya adalah ini:

  1. Sembunyikan semua item setelah tanggal 4
  2. Tambahkan ...titik menggunakan beforeelemen pseudo.
  3. Saat mengarahkan titik-titik (atau elemen tersembunyi), perlihatkan item tambahan alias submenu.

Berikut adalah kode CSS untuk markup menu WordPress default. Saya sudah berkomentar sebaris.

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

Saya juga membuat jsfiddle untuk menunjukkannya dalam tindakan: http://jsfiddle.net/jg6pLfd1/

Jika Anda memiliki pertanyaan lebih lanjut tentang cara kerjanya, silakan tinggalkan komentar, dengan senang hati saya akan menjelaskan kode lebih lanjut.

pembuat kraftner
sumber
Terima kasih atas pendekatan Anda. Saya sudah berpikir untuk melakukannya dengan CSS tapi saya pikir lebih bersih untuk melakukannya langsung di dalam php. Tambahan solusi ini menempatkan titik menu 5 ke dalam submenu, juga tidak perlu jika hanya ada lima item menu.
Bola Salju
Yah hanya mengaktifkannya untuk 5+ item mungkin bisa diperbaiki. Pokoknya saya sadar bahwa ini tidak sempurna dan pendekatan PHP mungkin lebih bersih. Tapi saya masih merasa cukup menarik untuk memasukkannya demi kelengkapan. Pilihan lain selalu menyenangkan. :)
kraftner
2
Tentu saja. Btw. jika Anda menambahkan submenu lain ke dalam submenu ini juga rusak
Snowball
1
Tentu. Ini lebih merupakan bukti konsep sampai sekarang. Menambahkan peringatan.
kraftner
8

Anda bisa menggunakan wp_nav_menu_itemsfilter. Ini menerima output menu dan argumen yang menyimpan atribut menu, seperti menu siput, wadah, dll.

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>';
}
mjakic
sumber
1
Saya telah mengedit beberapa masalah kecil, namun ini hanya berfungsi jika semua item menu tidak memiliki sub item. Karena Regex tidak mengenal hierarki. Mengujinya: jika salah satu dari 4 item menu pertama berisi item anak, menu ini cukup hancur.
gmazzap
1
Itu benar. Dalam hal itu DOMDocumentbisa digunakan. Namun, dalam pertanyaan ini tidak ada sub menu, jadi jawabannya benar untuk kasus khusus ini. DOMDocument akan menjadi solusi "universal" tapi saya tidak punya waktu sekarang. Anda dapat menyelidiki;) mengulang-ulang item LI, jika seseorang memiliki anak UL lewati, itu akan menjadi solusi tetapi perlu versi tertulis :)
mjakic
1
(a) Anda tidak tahu apakah ada submenu di OP. Submenu muncul ketika mouse selesai, jadi ... (b) Ya, DOMDocument mungkin berfungsi, tetapi dalam hal ini Anda perlu mengulang item secara berulang untuk melakukan pemeriksaan bagian dalam ul. WordPress sudah meng-loop item-item menu di menu walker. Ini sudah merupakan operasi yang lambat per se , menambah dan menambah lingkaran saya pikir bukan solusi yang tepat, sebaliknya, walker kustom akan menjadi solusi yang jauh lebih bersih dan efisien.
gmazzap
Terima kasih teman-teman, tetapi @gmazzap benar, ada kemungkinan bahwa poin menu lainnya (baik yang pertama 4 atau yang lain) mengandung submenu lain. Jadi soultion ini tidak akan berfungsi.
Bola Salju
Anda juga dapat menempatkan dua menu, satu menu utama dan menu "tersembunyi". Tambahkan tombol gaya dengan tiga titik "..." dan pada klik atau arahkan menu kedua. Seharusnya super mudah.
mjakic
5

Punya fungsi yang berfungsi, tetapi tidak yakin apakah itu solusi terbaik.

Saya menggunakan alat bantu khusus:

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

  }
}

Fungsi yang menunjukkan menu aktual adalah sebagai berikut:

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

Saya mendeklarasikan variabel global $ menu_items dan menggunakannya untuk menunjukkan penutupan <li>dan <ul>-tag. Mungkin saja bisa dilakukan di dalam custom walker, tapi saya tidak menemukan di mana dan bagaimana.

Dua masalah: 1. Jika hanya ada 5 Item di Menu, itu membungkus item terakhir juga menjadi meskipun tidak perlu.

  1. Ini hanya berfungsi jika pengguna telah benar-benar mengalokasikan menu ke theme_location, walker tidak memecat jika wp_nav_menu menunjukkan fungsi mundur
Semakin bertambah
sumber
Sudahkah Anda mencoba apa yang terjadi jika salah satu dari 4 item pertama memiliki beberapa submenu? Kiat: substr_count($output,'<li')akan berada == 4di tempat yang salah ...
gmazzap