커스텀 워커로 wp_nav_menu 분할


16

최대 5 개의 항목을 표시하는 메뉴를 만들려고합니다. 항목이 더 있으면 다른 <ul>요소 로 감싸서 드롭 다운을 만들어야합니다.

5 항목 이하

쓰러지 다

6 항목 이상

쓰러지 다

메뉴 항목을 세고 나머지에 5 개 이상이 있으면 랩핑하는 워커로 이러한 종류의 기능을 쉽게 만들 수 있습니다 <ul>. 그러나 나는이 워커를 만드는 방법을 모른다.

현재 내 메뉴를 표시하는 코드는 다음과 같습니다.

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

메뉴가 사용자가 정의하지 않고 대신 폴백 기능을 사용하면 워커가 영향을 미치지 않습니다. 두 경우 모두 작동해야합니다.


1
사용자 정의 메뉴 워커는 확장하는 클래스입니다 Walker_Nav_Menu분과의 예는있다 . "Walker를 만드는 방법을 모르겠습니다"는 무슨 뜻입니까?
cybmeta

아이디어가 실제로 굉장하기 때문에 Btw, +1. 어떻게 넘어 졌습니까? 소스 게시물이 있습니까? 그렇다면 기꺼이 읽어 드리겠습니다. 미리 감사드립니다.
kaiser

@kaiser 그냥 불쾌한 디자인 아이디어 :) 소스 게시물이 없으므로 그 이유를 묻습니다.
Snowball

@cybmeta 워커를 만들고 코덱에 예제가 있다는 것을 알고 있지만이 특정 문제에 대한 예제는 없습니다. 그래서 나에게 해결책을주는 커스텀 워커를 만드는 법을 모른다
Snowball

UX.SE 담당자 에게이 아이디어에 대해 문의하고 사용자에게 문제가 있는지 확인해야합니다. UX는 유용성 / 경험에 대한 실질적인 확인을 제공하고 정기적으로 잘 생각되는 답변과 문제를 제공하는 정말 멋진 사이트입니다. 당신은 다시 돌아올 수 있고 우리 모두 그 아이디어를 다듬습니다. (정말 멋질 것입니다!).
카이저

답변:


9

사용자 정의 Walker를 사용하면 start_el()메소드가 $depthparam에 액세스 할 수 있습니다 0. elemnt가 최상위 인 경우이 정보를 사용하여 내부 카운터를 유지할 수 있습니다.

카운터가 한도에 도달 DOMDocument하면 마지막으로 추가 한 요소 만 전체 HTML 출력에서 ​​가져 와서 하위 메뉴에 싸서 HTML에 다시 추가하는 데 사용할 수 있습니다.


편집하다

요소의 수가 정확히 + 1 필요한 숫자 + 예를 들어 5 개의 요소가 표시되어야하고 메뉴에 6이있는 경우 요소는 6 가지 방법이므로 메뉴를 분할하는 것은 의미가 없습니다. 이를 해결하기 위해 코드가 편집되었습니다.


코드는 다음과 같습니다.

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

사용법은 매우 간단합니다.

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

훌륭하게 작동합니다! 멋진 작품 주세페. 이것의 가장 큰 장점은 처음 5 개의 메뉴 요소에 하위 메뉴가있는 경우에도 잘 작동한다는 것입니다. 그리고 필요하지 않은 경우 단일 메뉴 포인트 만 하위 메뉴로 감싸지 않습니다. 사소한 일 : 기본적으로 6 개의 요소를 표시 $split_at = 5하지만 $count색인은 0에서 시작합니다.
Snowball

@ Snownow 감사합니다. 사소한 문제를 해결했습니다. 이제 메뉴에는 $split_at인수로 전달 된 정확한 숫자 가 기본적으로 5로 표시됩니다.
gmazzap

10

CSS만으로도 이것을 가능하게하는 방법이 있습니다. 여기에는 몇 가지 한계가 있지만 여전히 흥미로운 접근법이라고 생각했습니다.

한계

  • 드롭 다운 너비를 하드 코딩해야합니다
  • 브라우저 지원. 기본적으로 CSS3 선택기 가 필요합니다 . 그러나 이것을 테스트하지는 않았지만 IE8의 모든 것이 작동해야합니다.
  • 이것은 더 많은 개념 증명입니다. 하위 항목이없는 경우에만 작동하는 것과 같은 몇 가지 단점이 있습니다.

접근하다

"Quantity Queries"를 실제로 사용하지는 않지만 창의적 사용법을 사용 :nth-child하고 CSS~ 의 최근 Quantity Queries를 읽었습니다. 솔루션으로 이끌었습니다.

접근 방식은 기본적으로 다음과 같습니다.

  1. 4 일 이후에 모든 항목 숨기기
  2. ...사용하여 점 추가before의사 요소를 .
  3. 점 (또는 숨겨진 요소)을 가리키면 하위 메뉴라는 추가 항목이 표시됩니다.

기본 WordPress 메뉴 마크 업에 대한 CSS 코드는 다음과 같습니다. 인라인으로 댓글을 달았습니다.

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

나는 또한 그것을 보여주는 jsfiddle을 만들었습니다 : http://jsfiddle.net/jg6pLfd1/

이것이 어떻게 작동하는지 더 궁금한 점이 있으면 의견을 남겨주세요. 코드를 더 명확하게 설명 드리겠습니다.


접근 해 주셔서 감사합니다. CSS로 이미 생각했지만 PHP에서 직접 수행하는 것이 더 깨끗하다고 ​​생각합니다. 추가로이 솔루션은 5 번째 메뉴 포인트를 하위 메뉴에 넣습니다. 또한 5 개의 메뉴 항목 만 있으면 필요하지 않습니다.
Snowball

5 + 항목에만 활성화하면 수정 될 수 있습니다. 어쨌든 나는 이것이 완벽하지 않으며 PHP 접근 방식이 더 깨끗하다는 것을 알고 있습니다. 그러나 나는 여전히 완전성을 위해 그것을 포함시키기에 충분히 흥미로웠다. 다른 옵션은 항상 좋습니다. :)
kraftner

2
물론이야. Btw. 이것에 또 다른 하위 메뉴를 추가하면 역시 깨집니다
Snowball

1
확실한. 이것은 지금까지 개념 증명에 더 가깝습니다. 경고를 추가했습니다.
kraftner

8

wp_nav_menu_items필터 를 사용할 수 있습니다 . 메뉴 슬러그, 컨테이너 등과 같은 메뉴 속성을 보유하는 메뉴 출력 및 인수를 허용합니다.

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
몇 가지 사소한 문제를 편집했지만 모든 메뉴 항목에 하위 항목이없는 경우에만 작동합니다. 정규식은 계층 구조를 인식하지 않기 때문입니다. 그것을 테스트하십시오 : 처음 4 개의 메뉴 항목 중 하나에 하위 항목이 포함되어 있으면 메뉴가 완전히 파괴됩니다.
gmazzap

1
사실입니다. 이 경우 DOMDocument사용할 수 있습니다. 그러나이 질문에는 하위 메뉴가 없으므로이 특정 경우에 대한 답변이 정확합니다. DOMDocument는 "범용"솔루션이지만 지금은 시간이 없습니다. 당신이 조사 할 수있다) LI 아이템을 루핑하는 것, 만약 UL 자식이 그것을 생략한다면, 그것은 해결책이지만 서면 버전이 필요하다 :)
mjakic

1
(a) OP에 하위 메뉴가 있는지 알 수 없습니다. 마우스가 끝나면 하위 메뉴가 나타납니다. (b) 예, DOMDocument가 작동 할 수 있지만,이 경우 항목을 재귀 적으로 반복하여 inner 검사를 수행해야합니다 ul. WordPress는 이미 메뉴 워커에서 메뉴 항목을 반복합니다. 그것은 이미 느린 작동 그 자체 이며, 추가 및 추가 루프가 올바른 솔루션이 아니라고 생각합니다. 국가에서는 맞춤형 워커가 훨씬 깨끗하고 효율적인 솔루션입니다.
gmazzap

고마워.하지만 @gmazzap은 사실이지만 다른 메뉴 포인트 (첫 번째 4 또는 다른 메뉴 포인트)에 다른 하위 메뉴가 포함될 가능성이 있습니다. 그래서이 영혼은 작동하지 않습니다.
Snowball

메인 메뉴와 "숨겨진"메뉴 두 개를 배치 할 수도 있습니다. 세 개의 점 "..."이있는 스타일이 지정된 단추를 추가하고 클릭하거나 마우스를 올리면 두 번째 메뉴가 표시됩니다. 매우 쉬워야합니다.
mjakic

5

작동하는 기능이 있지만 최상의 솔루션인지 확실하지 않습니다.

커스텀 워커를 사용했습니다.

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

  }
}

실제 메뉴를 표시하는 기능은 다음과 같습니다.

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

전역 변수 $ menu_items를 선언하고 닫기 <li><ul>-tags 를 표시하는 데 사용했습니다 . 커스텀 워커 내부에서도 그렇게 할 수는 있지만 어디에서 어떻게 찾을 수 없었습니다.

두 가지 문제 : 1. 메뉴에 5 개의 항목 만 있으면 마지막 항목은 물론 필요하지 않은 항목으로 감 쌉니다.

  1. 사용자가 theme_location에 메뉴를 실제로 할당 한 경우에만 작동하며 wp_nav_menu가 폴백 기능을 표시하면 워커가 실행되지 않습니다.

처음 4 개 항목 중 일부에 하위 메뉴가 있으면 어떻게됩니까? 팁 : 잘못된 장소에 substr_count($output,'<li')있을 것 == 4입니다.
gmazzap
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.