Tách wp_nav_menu với walker tùy chỉnh


16

Tôi đang cố gắng tạo một Menu hiển thị tối đa 5 mục. Nếu có nhiều vật phẩm hơn, nó nên bọc chúng vào một <ul>Element khác để tạo ra một danh sách thả xuống.

5 mục trở xuống:

Thả xuống

6 mục trở lên

Thả xuống

Tôi biết loại chức năng này có thể dễ dàng được tạo ra với một máy đi bộ đếm các mục menu và kết thúc tốt đẹp nếu có nhiều hơn 5 phần còn lại vào một ngăn cách <ul>. Nhưng tôi không biết làm thế nào để tạo ra walker này.

Mã hiển thị menu của tôi tại thời điểm này là như sau:

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

Tôi nhận thấy rằng nếu menu không được xác định bởi người dùng và nó sử dụng chức năng dự phòng thay vì walker không có hiệu lực. Tôi cần nó để làm việc trong cả hai trường hợp.


1
Trình đơn menu tùy chỉnh là một lớp mở rộng Walker_Nav_Menucó một ví dụ trong bộ mã . Ý bạn là gì với "Tôi không biết cách tạo Walker"?
cybmeta

Btw, +1 vì ý tưởng thực sự khá tuyệt vời. Làm thế nào bạn vấp ngã trên nó? Bạn có một bài viết nguồn hoặc một cái gì đó? Nếu vậy, tôi rất vui khi đọc nó. Cảm ơn trước.
kaiser

@kaiser chỉ là một ý tưởng thiết kế khó chịu :) không có bài đăng nguồn, đó là lý do tại sao tôi hỏi.
Snowball

@cybmeta Tôi biết để tạo walker và cũng có một ví dụ trong codex, nhưng không có ví dụ nào cho vấn đề cụ thể này. Vì vậy, tôi không biết làm thế nào để tạo một máy đi bộ tùy chỉnh mang đến cho tôi một giải pháp
Snowball

Bạn nên hỏi các chàng trai UX.SE về ý tưởng này và xác minh xem có vấn đề gì với người dùng không. UX là một trang web thực sự tuyệt vời mang lại một kiểm tra thực tế khá tốt về khả năng sử dụng / kinh nghiệm và thường xuyên suy nghĩ tốt câu trả lời và các vấn đề. Bạn cũng có thể quay lại và tất cả chúng ta cùng nhau tinh chỉnh ý tưởng đó. (điều đó sẽ thực sự tuyệt vời!).
kaiser

Câu trả lời:


9

Sử dụng Walker tùy chỉnh, start_el()phương thức này có quyền truy cập vào $depthparam: khi đó là 0elemnt là một ứng dụng hàng đầu và chúng ta có thể sử dụng thông tin này để duy trì bộ đếm nội bộ.

Khi bộ đếm đạt đến giới hạn, chúng ta có thể sử dụng DOMDocumentđể lấy từ đầu ra HTML đầy đủ chỉ phần tử cuối cùng được thêm vào, bọc nó trong menu con và thêm lại vào HTML.


Biên tập

Khi số lượng phần tử chính xác là số lượng chúng tôi yêu cầu + 1, ví dụ: chúng tôi yêu cầu 5 phần tử hiển thị và menu có 6, sẽ không có ý nghĩa gì khi chia menu, bởi vì các phần tử sẽ là 6. Mã đã được chỉnh sửa để giải quyết điều đó.


Đây là mã:

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

Cách sử dụng khá đơn giản:

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

Hoạt động xuất sắc! Giuseppe làm việc tuyệt vời. Điều tuyệt vời về nó là nó hoạt động tốt nếu có một menu con trong 5 thành phần menu đầu tiên. Và nó không bao bọc chỉ một menu duy nhất vào menu con nếu không có nhu cầu. Chỉ là một điều nhỏ: Theo mặc định, nó hiển thị 6 yếu tố $split_at = 5nhưng $countchỉ số bắt đầu từ 0.
Snowball

Cảm ơn @Snowball Tôi đã khắc phục sự cố nhỏ đó, hiện tại menu hiển thị số chính xác được truyền dưới dạng $split_atđối số, 5 theo mặc định.
gmazzap

10

Thậm chí còn có một cách để thực hiện điều này chỉ với CSS. Điều này có một số hạn chế, nhưng tôi vẫn nghĩ rằng nó có thể là một cách tiếp cận thú vị:

Hạn chế

  • Bạn cần mã hóa độ rộng của danh sách thả xuống
  • Hỗ trợ trình duyệt. Về cơ bản bạn cần bộ chọn CSS3 . Nhưng mọi thứ từ IE8 trở lên đều hoạt động, mặc dù tôi chưa thử nghiệm điều này.
  • Đây là nhiều hơn một bằng chứng khái niệm. Có một số nhược điểm như chỉ hoạt động nếu không có mục phụ.

Tiếp cận

Mặc dù tôi không thực sự sử dụng "Truy vấn số lượng", việc sử dụng sáng tạo :nth-child~tôi đã đọc trong Truy vấn số lượng gần đây cho CSS là điều đã dẫn tôi đến giải pháp này.

Cách tiếp cận về cơ bản là thế này:

  1. Ẩn tất cả các mục sau lần thứ 4
  2. Thêm ...dấu chấm bằng beforephần tử giả.
  3. Khi di chuột vào các dấu chấm (hoặc bất kỳ yếu tố ẩn nào) sẽ hiển thị các mục phụ hay còn gọi là menu con.

Đây là mã CSS cho một đánh dấu menu mặc định của WordPress. Tôi đã nhận xét nội tuyến.

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

Tôi cũng đã tạo một jsfiddle để hiển thị nó trong hành động: http://jsfiddle.net/jg6pLfd1/

Nếu bạn có thêm bất kỳ câu hỏi nào về cách thức hoạt động của nó, vui lòng để lại nhận xét, tôi rất vui lòng làm rõ mã hơn nữa.


Cảm ơn cách tiếp cận của bạn. Tôi đã nghĩ làm việc đó với CSS nhưng tôi nghĩ rằng trình dọn dẹp này sẽ thực hiện trực tiếp trong php. Bổ sung giải pháp này đặt điểm menu thứ 5 vào menu con, cũng không cần nếu chỉ có năm mục menu.
Snowball

Vâng chỉ cho phép nó cho hơn 5 mặt hàng có thể được sửa chữa. Dù sao, tôi biết rằng điều này không hoàn hảo và cách tiếp cận PHP có thể sạch hơn. Nhưng tôi vẫn thấy nó đủ thú vị để bao gồm nó vì lợi ích hoàn chỉnh. Một lựa chọn khác luôn luôn tốt đẹp. :)
kraftner 6/03/2015

2
Tất nhiên. Btw. nếu bạn thêm một menu con khác vào cái này thì nó cũng bị hỏng
Snowball

1
Chắc chắn rồi. Đây là nhiều bằng chứng về khái niệm cho đến bây giờ. Đã thêm một cảnh báo.
kraftner

8

Bạn có thể sử dụng wp_nav_menu_itemsbộ lọc. Nó chấp nhận đầu ra menu và các đối số chứa các thuộc tính menu, như slug menu, container, v.v.

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
Tôi đã chỉnh sửa một vài vấn đề nhỏ, tuy nhiên điều này chỉ hoạt động nếu tất cả các mục menu không có mục phụ. Bởi vì Regex không công nhận thứ bậc. Kiểm tra xem: nếu bất kỳ mục nào trong 4 mục menu đầu tiên chứa một mục con, thì menu sẽ bị hủy hoàn toàn.
gmazzap

1
Đúng. Trong trường hợp đó DOMDocumentcó thể được sử dụng. Tuy nhiên, trong câu hỏi này không có menu phụ, do đó câu trả lời là đúng cho trường hợp cụ thể này. DOMDocument sẽ là giải pháp "phổ quát" nhưng hiện tại tôi không có thời gian. Bạn có thể điều tra;) lặp qua các mục LI, nếu một người có con UL bỏ qua, đó sẽ là giải pháp nhưng cần phiên bản bằng văn bản :)
mjakic

1
(a) bạn không biết nếu có menu con trong OP. Các menu con xuất hiện khi chuột kết thúc, vì vậy ... (b) Có, DOMDocument có thể hoạt động, nhưng trong trường hợp đó, bạn cần lặp lại các mục để thực hiện kiểm tra bên trong ul. WordPress đã lặp lại các mục menu trong menu walker. Nó đã là một hoạt động chậm trên mỗi se , thêm và vòng lặp bổ sung Tôi nghĩ rằng đó không phải là giải pháp phù hợp, trên phương diện chính xác, một máy đi bộ tùy chỉnh sẽ là giải pháp hiệu quả và sạch sẽ hơn nhiều.
gmazzap

Cảm ơn các bạn, nhưng @gmazzap là đúng, có khả năng các menu khác (cả 4 đầu tiên hoặc các cái khác) đều chứa một menu con khác. Vì vậy, linh hồn này sẽ không hoạt động.
Snowball

Bạn cũng có thể đặt hai menu, một chính và một "ẩn". Thêm một nút theo kiểu có ba dấu chấm "..." và khi nhấp hoặc di chuột hiển thị menu thứ hai. Nên siêu dễ.
mjakic

5

Có một chức năng làm việc, nhưng không chắc chắn nếu đó là giải pháp tốt nhất.

Tôi đã sử dụng một walker tùy chỉnh:

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

  }
}

Các chức năng hiển thị menu thực tế là như sau:

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

Tôi đã khai báo biến toàn cục $ menu_items và sử dụng nó để hiển thị kết thúc <li><ul>-tags. Rất có thể nó cũng có thể làm điều đó bên trong khung tập đi tùy chỉnh, nhưng tôi không tìm thấy ở đâu và như thế nào.

Hai vấn đề: 1. Nếu chỉ có 5 Mục trong Menu, nó cũng bọc mục cuối cùng vào một mặc dù không cần nó.

  1. Nó chỉ hoạt động nếu người dùng thực sự đã phân bổ một menu cho theme_location, walker không kích hoạt nếu wp_nav_menu hiển thị chức năng dự phòng

Bạn đã thử những gì xảy ra nếu bất kỳ trong 4 mục đầu tiên có một số menu con? Mẹo: substr_count($output,'<li')sẽ == 4ở sai vị trí ...
gmazzap
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.